in-toto / go-witness

Go implementation of witness
Apache License 2.0
26 stars 21 forks source link

[Question] What is the canonical way to unmarshal "https://witness.testifysec.com/attestation-collection/v0.1" #376

Open xNok opened 1 month ago

xNok commented 1 month ago

I have started using Witness to produce attestations for builds, and now I would like to be able to parse those attestations to extract relevant metadata to display in other systems.

I didn't really know where to start so I looked into in-toto spec and landed on:

Parsing the attestation.Collection feels very tedious, and I hope there is a better way.

// parse the manifest envelope
    var envelope dsse.Envelope
    err := json.Unmarshal(att, &envelope)
    if err != nil {
        return fmt.Errorf("failed to artefact: %w", err)
    }

    if envelope.PayloadType != "application/vnd.in-toto+json" {
        return fmt.Errorf("cant't decode payload type %s", envelope.PayloadType)
    }

    var statement intoto.Statement
    err = protojson.Unmarshal(envelope.Payload, &statement)
    if err != nil {
        return fmt.Errorf("failed to unmarshal statement payload: %w", err)
    }

    if statement.PredicateType != attestation.CollectionType {
        return fmt.Errorf("expected '%s' type, got %s", attestation.CollectionType, statement.PredicateType)
    }

    jsonPredicate, err := statement.Predicate.MarshalJSON()
    if err != nil {
        return fmt.Errorf("failed to remarshal predicate: %w", err)
    }

    var collection attestation.Collection
    err = json.Unmarshal(jsonPredicate, &collection)
    if err != nil {
        return fmt.Errorf("failed to collection predicate: %w", err)
    }

Even at that point, I was not able to directly parse individual attestation because of the following error:

failed to collection predicate: attestation not found: https://witness.dev/attestations/gitlab/v0.1

This seems to be caused by the attestationsByType map being empty.

All this leads me to deliver that this is not the intended way to parse attestation with the library.

xNok commented 1 month ago

I found the answer in the end, two catches:

_ "github.com/in-toto/go-witness"

So it can be done in four steps:

env := dsse.Envelope{}
    if err := json.Unmarshal(data, &env); err != nil {
        return errors.Join(ErrFailedToProcessInTotoAttestation, err)
    }

    statement := intoto.Statement{}
    if err := json.Unmarshal(env.Payload, &statement); err != nil {
        return errors.Join(ErrFailedToProcessInTotoAttestation, err)
    }

    collection := attestation.Collection{}
    if err := json.Unmarshal(statement.Predicate, &collection); err != nil {
        return errors.Join(ErrFailedToProcessInTotoAttestation, err)
    }

    var attestationProcessingErrors error
    for _, att := range collection.Attestations {
        val, ok := h.AttestationHandlers[att.Type]
        if !ok {
            continue
        }

        err := val.Handle(att.Attestation, result)
        if err != nil {
            attestationProcessingErrors = errors.Join(attestationProcessingErrors, fmt.Errorf("error processing attestation %s: %w", att.Type, err))
        }
    }

I do think the lib should provide simple methods to parse the attestation collection. I haven't yet looked at the verification, but I was hoping to get both (verification and metadata) out of a simple interface.