Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ctclient to support SCT extensions #1645

Merged
merged 2 commits into from
Feb 3, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## HEAD

* Update ctclient tool to support SCT extensions field by @liweitianux in https://github.com/google/certificate-transparency-go/pull/1645

## v1.3.1

* Add AllLogListSignatureURL by @AlexLaroche in https://github.com/google/certificate-transparency-go/pull/1634
Expand Down
3 changes: 3 additions & 0 deletions client/ctclient/cmd/get_entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func showRawLogEntry(rle *ct.RawLogEntry) {
ts := rle.Leaf.TimestampedEntry
when := ct.TimestampToTime(ts.Timestamp)
fmt.Printf("Index=%d Timestamp=%d (%v) ", rle.Index, ts.Timestamp, when)
if len(ts.Extensions) > 0 {
fmt.Printf("Extensions=%x ", ts.Extensions)
}

switch ts.EntryType {
case ct.X509LogEntryType:
Expand Down
43 changes: 34 additions & 9 deletions client/ctclient/cmd/get_inclusion_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/pem"
"fmt"
"os"
Expand All @@ -35,15 +36,16 @@ import (
)

var (
leafHash string
certChain string
timestamp int64
treeSize uint64
leafHash string
certChain string
timestamp int64
treeSize uint64
extensions string
)

func init() {
cmd := cobra.Command{
Use: fmt.Sprintf("get-inclusion-proof %s {--leaf_hash=hash | --cert_chain=file} [--timestamp=ts] [--size=N]", connectionFlags),
Use: fmt.Sprintf("get-inclusion-proof %s {--leaf_hash=hash | --cert_chain=file} [--timestamp=ts] [--size=N] [--extensions=exts]", connectionFlags),
Aliases: []string{"getinclusionproof", "inclusion-proof", "inclusion"},
Short: "Fetch and verify the inclusion proof for an entry",
Args: cobra.MaximumNArgs(0),
Expand All @@ -54,13 +56,22 @@ func init() {
cmd.Flags().StringVar(&leafHash, "leaf_hash", "", "Leaf hash to retrieve (as hex string or base64)")
cmd.Flags().StringVar(&certChain, "cert_chain", "", "Name of file containing certificate chain as concatenated PEM files")
cmd.Flags().Int64Var(&timestamp, "timestamp", 0, "Timestamp to use for inclusion checking")
cmd.Flags().StringVar(&extensions, "extensions", "", "CT extensions in SCT (as hex string)")
cmd.Flags().Uint64Var(&treeSize, "size", 0, "Tree size to query at")
rootCmd.AddCommand(&cmd)
}

// runGetInclusionProof runs the get-inclusion-proof command.
func runGetInclusionProof(ctx context.Context) {
logClient := connect(ctx)
var ctexts ct.CTExtensions
liweitianux marked this conversation as resolved.
Show resolved Hide resolved
if len(extensions) > 0 {
exts, err := hex.DecodeString(extensions)
if err != nil {
klog.Exitf("Invalid --extensions supplied: %v", err)
}
ctexts = ct.CTExtensions(exts)
}
var hash []byte
if len(leafHash) > 0 {
var err error
Expand All @@ -70,13 +81,16 @@ func runGetInclusionProof(ctx context.Context) {
}
} else if len(certChain) > 0 {
// Build a leaf hash from the chain and a timestamp.
chain, entryTimestamp := chainFromFile(certChain)
chain, entryTimestamp, entryCTExts := chainFromFile(certChain)
if timestamp != 0 {
entryTimestamp = timestamp // Use user-specified timestamp.
}
if entryTimestamp == 0 {
klog.Exit("No timestamp available to accompany certificate")
}
if len(entryCTExts) == 0 {
entryCTExts = ctexts
}

var leafEntry *ct.MerkleTreeLeaf
cert, err := x509.ParseCertificate(chain[0].Data)
Expand All @@ -92,6 +106,7 @@ func runGetInclusionProof(ctx context.Context) {
leafEntry = ct.CreateX509MerkleTreeLeaf(chain[0], uint64(entryTimestamp))
}

leafEntry.TimestampedEntry.Extensions = entryCTExts
leafHash, err := ct.LeafHashForLeaf(leafEntry)
if err != nil {
klog.Exitf("Failed to create hash of leaf: %v", err)
Expand Down Expand Up @@ -139,7 +154,7 @@ func getInclusionProofForHash(ctx context.Context, logClient client.CheckLogClie
}
}

func chainFromFile(filename string) ([]ct.ASN1Cert, int64) {
func chainFromFile(filename string) ([]ct.ASN1Cert, int64, []byte) {
contents, err := os.ReadFile(filename)
if err != nil {
klog.Exitf("Failed to read certificate file: %v", err)
Expand All @@ -160,9 +175,12 @@ func chainFromFile(filename string) ([]ct.ASN1Cert, int64) {
klog.Exitf("No certificates found in %s", certChain)
}

// Also look for something like a text timestamp for convenience.
// Also look for something like a text timestamp and a hex extensions
// for convenience.
var timestamp int64
var extensions []byte
tsRE := regexp.MustCompile(`Timestamp[:=](\d+)`)
extsRE := regexp.MustCompile(`Extensions[:=]([a-fA-F0-9]+)`)
for _, line := range strings.Split(string(contents), "\n") {
x := tsRE.FindStringSubmatch(line)
if len(x) > 1 {
Expand All @@ -171,6 +189,13 @@ func chainFromFile(filename string) ([]ct.ASN1Cert, int64) {
break
}
}
x = extsRE.FindStringSubmatch(line)
if len(x) > 1 {
extensions, err = hex.DecodeString(x[1])
if err != nil {
break
}
}
}
return chain, timestamp
return chain, timestamp, extensions
}
8 changes: 7 additions & 1 deletion client/ctclient/cmd/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func runUpload(ctx context.Context) {
if certChain == "" {
klog.Exitf("No certificate chain file specified with -cert_chain")
}
chain, _ := chainFromFile(certChain)
chain, _, _ := chainFromFile(certChain)

// Examine the leaf to see if it looks like a pre-certificate.
isPrecert := false
Expand All @@ -74,6 +74,7 @@ func runUpload(ctx context.Context) {
}
// Calculate the leaf hash.
leafEntry := ct.CreateX509MerkleTreeLeaf(chain[0], sct.Timestamp)
leafEntry.TimestampedEntry.Extensions = sct.Extensions
leafHash, err := ct.LeafHashForLeaf(leafEntry)
if err != nil {
klog.Exitf("Failed to create hash of leaf: %v", err)
Expand All @@ -84,6 +85,11 @@ func runUpload(ctx context.Context) {
fmt.Printf("Uploaded chain of %d certs to %v log at %v, timestamp: %d (%v)\n", len(chain), sct.SCTVersion, logClient.BaseURI(), sct.Timestamp, when)
fmt.Printf("LogID: %x\n", sct.LogID.KeyID[:])
fmt.Printf("LeafHash: %x\n", leafHash)
if len(sct.Extensions) > 0 {
fmt.Printf("Extensions: %x\n", sct.Extensions)
} else {
fmt.Printf("Extensions: (nil)\n")
}
fmt.Printf("Signature: %v\n", signatureToString(&sct.Signature))

age := time.Since(when)
Expand Down
1 change: 1 addition & 0 deletions client/logclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype
if err != nil {
return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
}
leaf.TimestampedEntry.Extensions = sct.Extensions
entry := ct.LogEntry{Leaf: *leaf}
return c.Verifier.VerifySCTSignature(sct, entry)
}
Expand Down
Loading