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

RFC 3161 Timestamping Support #16

Merged
merged 1 commit into from
Oct 13, 2022
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
14 changes: 14 additions & 0 deletions cryptoutil/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,17 @@ func TryParseKeyFromReader(r io.Reader) (interface{}, error) {
pemBlock, _ := pem.Decode(bytes)
return TryParsePEMBlock(pemBlock)
}

func TryParseCertificate(data []byte) (*x509.Certificate, error) {
possibleCert, err := TryParseKeyFromReader(bytes.NewReader(data))
if err != nil {
return nil, err
}

cert, ok := possibleCert.(*x509.Certificate)
if !ok {
return nil, fmt.Errorf("data was a valid verifier but not a certificate")
}

return cert, nil
}
227 changes: 13 additions & 214 deletions dsse/dsse.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,7 @@
package dsse

import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"time"

"github.com/testifysec/go-witness/cryptoutil"
)

type ErrNoSignatures struct{}
Expand Down Expand Up @@ -61,12 +54,20 @@ type Envelope struct {
}

type Signature struct {
KeyID string `json:"keyid"`
Signature []byte `json:"sig"`
Certificate []byte `json:"certificate,omitempty"`
Intermediates [][]byte `json:"intermediates,omitempty"`
KeyID string `json:"keyid"`
Signature []byte `json:"sig"`
Certificate []byte `json:"certificate,omitempty"`
Intermediates [][]byte `json:"intermediates,omitempty"`
Timestamps []SignatureTimestamp `json:"timestamps,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a timestamp signature on the signature, correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct

}

type SignatureTimestampType string

const TimestampRFC3161 SignatureTimestampType = "tsp"

trustedTime time.Time
type SignatureTimestamp struct {
Type SignatureTimestampType `json:"type"`
Data []byte `json:"data"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this data encoded?

Copy link
Member Author

@mikhailswift mikhailswift Sep 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base64. Go json marshals all byte arrays to base64 strings.

Copy link
Member Author

@mikhailswift mikhailswift Sep 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as what data actually is that is an implementation detail of the timestamper being used.

In the case of TSP it’s an ASN1 DER encoded Timestamp Token object.

}

// preauthEncode wraps the data to be signed or verified and it's type in the DSSE protocol's
Expand All @@ -76,205 +77,3 @@ func preauthEncode(bodyType string, body []byte) []byte {
const dsseVersion = "DSSEv1"
return []byte(fmt.Sprintf("%s %d %s %d %s", dsseVersion, len(bodyType), bodyType, len(body), body))
}

// TODO: it'd be nice to break some of this logic out of what should be a presentation layer only
func Sign(bodyType string, body io.Reader, signers ...cryptoutil.Signer) (Envelope, error) {
env := Envelope{}
bodyBytes, err := io.ReadAll(body)
if err != nil {
return env, err
}

env.PayloadType = bodyType
env.Payload = bodyBytes
env.Signatures = make([]Signature, 0)
pae := preauthEncode(bodyType, bodyBytes)
for _, signer := range signers {
sig, err := signer.Sign(bytes.NewReader(pae))
if err != nil {
return env, err
}

keyID, err := signer.KeyID()
if err != nil {
return env, err
}

dsseSig := Signature{
KeyID: keyID,
Signature: sig,
}

if trustBundler, ok := signer.(cryptoutil.TrustBundler); ok {
leaf := trustBundler.Certificate()
intermediates := trustBundler.Intermediates()
if leaf != nil {
dsseSig.Certificate = pem.EncodeToMemory(&pem.Block{Type: PemTypeCertificate, Bytes: leaf.Raw})
}

for _, intermediate := range intermediates {
dsseSig.Intermediates = append(dsseSig.Intermediates, pem.EncodeToMemory(&pem.Block{Type: PemTypeCertificate, Bytes: intermediate.Raw}))
}
}

env.Signatures = append(env.Signatures, dsseSig)
}

return env, nil
}

type VerificationOption func(*verificationOptions)

type verificationOptions struct {
roots []*x509.Certificate
intermediates []*x509.Certificate
verifiers []cryptoutil.Verifier
threshold int
}

func WithRoots(roots []*x509.Certificate) VerificationOption {
return func(vo *verificationOptions) {
vo.roots = roots
}
}

func WithIntermediates(intermediates []*x509.Certificate) VerificationOption {
return func(vo *verificationOptions) {
vo.intermediates = intermediates
}
}

func WithVerifiers(verifiers []cryptoutil.Verifier) VerificationOption {
return func(vo *verificationOptions) {
vo.verifiers = verifiers
}
}

func WithThreshold(threshold int) VerificationOption {
return func(vo *verificationOptions) {
vo.threshold = threshold
}
}

func (e Envelope) Verify(opts ...VerificationOption) ([]cryptoutil.Verifier, error) {
options := &verificationOptions{
threshold: 1,
}

for _, opt := range opts {
opt(options)
}

if options.threshold <= 0 {
return nil, ErrInvalidThreshold(options.threshold)
}

pae := preauthEncode(e.PayloadType, e.Payload)
if len(e.Signatures) == 0 {
return nil, ErrNoSignatures{}
}

matchingSigFound := false
passedVerifiers := make([]cryptoutil.Verifier, 0)
for _, sig := range e.Signatures {
if sig.Certificate != nil && len(sig.Certificate) > 0 {
cert, err := TryParseCertificate(sig.Certificate)
if err != nil {
continue
}

sigIntermediates := make([]*x509.Certificate, 0)
for _, int := range sig.Intermediates {
intCert, err := TryParseCertificate(int)
if err != nil {
continue
}

sigIntermediates = append(sigIntermediates, intCert)
}

sigIntermediates = append(sigIntermediates, options.intermediates...)
verifier, err := cryptoutil.NewX509Verifier(cert, sigIntermediates, options.roots, sig.trustedTime)
if err != nil {
return nil, err
}

if err := verifier.Verify(bytes.NewReader(pae), sig.Signature); err == nil {
passedVerifiers = append(passedVerifiers, verifier)
matchingSigFound = true
}
}

for _, verifier := range options.verifiers {
if verifier != nil {
if err := verifier.Verify(bytes.NewReader(pae), sig.Signature); err == nil {
passedVerifiers = append(passedVerifiers, verifier)
matchingSigFound = true
}
}
}
}

if !matchingSigFound {
return nil, ErrNoMatchingSigs{}
}

if len(passedVerifiers) < options.threshold {
return passedVerifiers, ErrThresholdNotMet{Theshold: options.threshold, Acutal: len(passedVerifiers)}
}

return passedVerifiers, nil
}

func TryParseCertificate(data []byte) (*x509.Certificate, error) {
possibleCert, err := cryptoutil.TryParseKeyFromReader(bytes.NewReader(data))
if err != nil {
return nil, err
}

cert, ok := possibleCert.(*x509.Certificate)
if !ok {
return nil, fmt.Errorf("data was a valid verifier but not a certificate")
}

return cert, nil
}

type SignatureOption func(so *signatureOptions)

type signatureOptions struct {
cert []byte
intermediates [][]byte
trustedTime time.Time
}

func SignatureWithCertificate(certBytes []byte) SignatureOption {
return func(so *signatureOptions) {
so.cert = certBytes
}
}

func SignatureWithIntermediates(intermediates [][]byte) SignatureOption {
return func(so *signatureOptions) {
so.intermediates = intermediates
}
}
func SignatureWithTrustedTime(trustedTime time.Time) SignatureOption {
return func(so *signatureOptions) {
so.trustedTime = trustedTime
}
}
func NewSignature(keyID string, sig []byte, opts ...SignatureOption) Signature {
so := signatureOptions{}
for _, opt := range opts {
opt(&so)
}

return Signature{
KeyID: keyID,
Signature: sig,
Certificate: so.cert,
Intermediates: so.intermediates,
trustedTime: so.trustedTime,
}
}
Loading