Skip to content

Commit

Permalink
add a custom CT signer
Browse files Browse the repository at this point in the history
  • Loading branch information
phbnf committed Aug 8, 2024
1 parent 01cd7f9 commit 63f91cb
Showing 1 changed file with 110 additions and 0 deletions.
110 changes: 110 additions & 0 deletions personalities/sctfe/serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import (
"crypto"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"fmt"

"github.com/google/certificate-transparency-go/tls"
"github.com/transparency-dev/formats/log"
"golang.org/x/mod/sumdb/note"

ct "github.com/google/certificate-transparency-go"
)
Expand Down Expand Up @@ -64,3 +67,110 @@ func buildV1SCT(signer crypto.Signer, leaf *ct.MerkleTreeLeaf) (*ct.SignedCertif
Signature: digitallySigned,
}, nil
}

type RFC6962NoteSignature struct {
timestamp uint64
signature ct.DigitallySigned
}

// buildCTCheckpoints builds a https://c2sp.org/static-ct-api checkpoint.
// TODO(phboneff): add tests
func buildCTCheckpoint(signer crypto.Signer, size uint64, timeMilli uint64, hash []byte) ([]byte, error) {
sth := ct.SignedTreeHead{
Version: ct.V1,
TreeSize: size,
Timestamp: timeMilli,
}
copy(sth.SHA256RootHash[:], hash)

sthBytes, err := ct.SerializeSTHSignatureInput(sth)
if err != nil {
return nil, fmt.Errorf("ct.SerializeSTHSignatureInput(): %v", err)
}

h := sha256.New()
h.Write(sthBytes)
signature, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
if err != nil {
return nil, err
}

rfc6962Note := RFC6962NoteSignature{
timestamp: sth.Timestamp,
signature: ct.DigitallySigned{
Algorithm: tls.SignatureAndHashAlgorithm{
Hash: tls.SHA256,
Signature: tls.SignatureAlgorithmFromPubKey(signer.Public()),
},
Signature: signature,
},
}

sig, err := tls.Marshal(rfc6962Note)
if err != nil {
return nil, fmt.Errorf("couldn't encode RFC6962NoteSignature: %w", err)
}

return sig, nil
}

type CTSigner struct {
sthSigner crypto.Signer
origin string
keyHash uint32
timeSource TimeSource
}

// Sign takes an unsigned checkpoint, and signs it with a https://c2sp.org/static-ct-api signature.
// Returns an error if the message doesn't parse as a checkpoint, or if the
// checkpoint origin doesn't match with the Signer's origin.
// TODO(phboneff): add tests
func (cts *CTSigner) Sign(msg []byte) ([]byte, error) {
ckpt := &log.Checkpoint{}
rest, err := ckpt.Unmarshal(msg)

if len(rest) != 0 {
return nil, fmt.Errorf("checkpoint contains trailing data: %s", string(rest))
} else if err != nil {
return nil, fmt.Errorf("ckpt.Unmarshal: %v", err)
}
if ckpt.Origin != cts.origin {
return nil, fmt.Errorf("checkpoint's origin %s doesn't match signer's origin %s", ckpt.Origin, cts.origin)
}

// TODO(phboneff): make sure that it's ok to generate the timestamp here
t := uint64(cts.timeSource.Now().UnixMilli())
sig, err := buildCTCheckpoint(cts.sthSigner, ckpt.Size, t, ckpt.Hash[:])
if err != nil {
return nil, fmt.Errorf("coudn't sign CT checkpoint: %v", err)
}
return sig, nil
}

func (cts *CTSigner) Name() string {
return cts.origin
}

func (cts *CTSigner) KeyHash() uint32 {
return cts.keyHash
}

// NewCTSigner returns a new note signer that can sign static-ct-api checkpoints
// according to https://c2sp.org/static-ct-api.
// TODO(phboneff): add tests
func NewCTSigner(signer crypto.Signer, origin string, logID [32]byte, timeSource TimeSource) note.Signer {
h := sha256.New()
h.Write([]byte(origin))
h.Write([]byte{0x0A}) // newline
h.Write([]byte{0x05}) // signature type
h.Write(logID[:])
sum := h.Sum(nil)

ctSigner := &CTSigner{
sthSigner: signer,
origin: origin,
keyHash: binary.BigEndian.Uint32(sum),
timeSource: timeSource,
}
return ctSigner
}

0 comments on commit 63f91cb

Please sign in to comment.