policy: add HTTP PGP signature verification builtin

Add verify_http_pgp_signature Rego builtin for HTTP sources using
pgpsign with checksum-request/response flow through policy resolution.
Wire sourcemeta HTTP checksum request/response conversion and add tests.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-03-26 15:39:46 -07:00
parent 55e8fa3b8e
commit a57372e966
9 changed files with 468 additions and 16 deletions
+1 -1
View File
@@ -5,6 +5,7 @@ go 1.25.5
require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/Microsoft/go-winio v0.6.2
github.com/ProtonMail/go-crypto v1.3.0
github.com/aws/aws-sdk-go-v2/config v1.32.12
github.com/compose-spec/compose-go/v2 v2.9.1
github.com/containerd/console v1.0.5
@@ -78,7 +79,6 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-cidr v1.0.1 // indirect
+138 -5
View File
@@ -3,11 +3,13 @@ package policy
import (
"bytes"
"context"
"crypto"
"encoding/json"
"fmt"
"io"
"maps"
"net/url"
"slices"
"strings"
"github.com/distribution/reference"
@@ -15,8 +17,10 @@ import (
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/gitutil/gitsign"
"github.com/moby/buildkit/util/pgpsign"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/rego"
"github.com/open-policy-agent/opa/v1/types"
@@ -26,11 +30,12 @@ import (
)
const (
funcLoadJSON = "load_json"
funcVerifyGitSignature = "verify_git_signature"
funcPinImage = "pin_image"
funcArtifactAttestation = "artifact_attestation"
funcGithubAttestation = "github_attestation"
funcLoadJSON = "load_json"
funcVerifyGitSignature = "verify_git_signature"
funcVerifyHTTPPGPSignature = "verify_http_pgp_signature"
funcPinImage = "pin_image"
funcArtifactAttestation = "artifact_attestation"
funcGithubAttestation = "github_attestation"
)
func (p *Policy) initBuiltinFuncs() {
@@ -69,6 +74,27 @@ func (p *Policy) initBuiltinFuncs() {
},
})
verifyHTTPPGPSignature := &rego.Function{
Name: funcVerifyHTTPPGPSignature,
Decl: types.NewFunction(
types.Args(
types.A,
types.S,
types.S,
),
types.B,
),
Memoize: false, // TODO:optimize
}
p.funcs = append(p.funcs, fun{
decl: verifyHTTPPGPSignature,
impl: func(s *state) func(*rego.Rego) {
return rego.Function3(verifyHTTPPGPSignature, func(bctx rego.BuiltinContext, a1 *ast.Term, a2 *ast.Term, a3 *ast.Term) (*ast.Term, error) {
return p.builtinVerifyHTTPPGPSignatureImpl(bctx, a1, a2, a3, s)
})
},
})
pinImageDigest := &rego.Function{
Name: funcPinImage,
Decl: types.NewFunction(
@@ -482,6 +508,113 @@ func (p *Policy) builtinVerifyGitSignatureImpl(_ rego.BuiltinContext, a1, a2 *as
return ast.BooleanTerm(true), nil
}
func (p *Policy) builtinVerifyHTTPPGPSignatureImpl(_ rego.BuiltinContext, a1, a2, a3 *ast.Term, s *state) (*ast.Term, error) {
inp := s.Input
if inp.HTTP == nil {
return ast.BooleanTerm(false), nil
}
obja, ok := a1.Value.(ast.Object)
if !ok {
return nil, errors.Errorf("%s: expected object, got %T", funcVerifyHTTPPGPSignature, a1.Value)
}
httpValue, err := ast.InterfaceToValue(inp.HTTP)
if err != nil {
return nil, errors.Wrapf(err, "%s: failed converting object to interface", funcVerifyHTTPPGPSignature)
}
if obja.Compare(httpValue) != 0 {
return nil, errors.Errorf("%s: first argument is not the same as input http", funcVerifyHTTPPGPSignature)
}
sigPath, ok := a2.Value.(ast.String)
if !ok {
return nil, errors.Errorf("%s: expected string signature path, got %T", funcVerifyHTTPPGPSignature, a2.Value)
}
pubKeyPath, ok := a3.Value.(ast.String)
if !ok {
return nil, errors.Errorf("%s: expected string pubkey path, got %T", funcVerifyHTTPPGPSignature, a3.Value)
}
signatureData, err := p.readFile(string(sigPath), 512*1024)
if err != nil {
return nil, err
}
pubKeyData, err := p.readFile(string(pubKeyPath), 512*1024)
if err != nil {
return nil, err
}
sig, _, err := pgpsign.ParseArmoredDetachedSignature(signatureData)
if err != nil {
return nil, errors.Wrapf(err, "%s: failed to parse detached signature", funcVerifyHTTPPGPSignature)
}
keyring, err := pgpsign.ReadAllArmoredKeyRings(pubKeyData)
if err != nil {
return nil, errors.Wrapf(err, "%s: failed to read armored keyring", funcVerifyHTTPPGPSignature)
}
algo, err := toPBChecksumAlgo(sig.Hash)
if err != nil {
return nil, errors.Wrapf(err, "%s: unsupported signature hash", funcVerifyHTTPPGPSignature)
}
suffix := slices.Clone(sig.HashSuffix)
checksumReq := &gwpb.ChecksumRequest{Algo: algo, Suffix: suffix}
resp := inp.HTTP.checksumResponseForSignature
if resp == nil || resp.Digest == "" {
s.checksumNeededForSignature = checksumReq
s.addUnknown(funcVerifyHTTPPGPSignature)
return ast.BooleanTerm(false), nil
}
if !bytes.Equal(resp.Suffix, suffix) {
s.checksumNeededForSignature = checksumReq
s.addUnknown(funcVerifyHTTPPGPSignature)
return ast.BooleanTerm(false), nil
}
dgst, err := digest.Parse(resp.Digest)
if err != nil {
return nil, errors.Wrapf(err, "%s: invalid checksum digest", funcVerifyHTTPPGPSignature)
}
if !checksumAlgoMatches(algo, dgst.Algorithm()) {
s.checksumNeededForSignature = checksumReq
s.addUnknown(funcVerifyHTTPPGPSignature)
return ast.BooleanTerm(false), nil
}
if err := pgpsign.VerifySignatureWithDigest(sig, keyring, dgst); err != nil {
return ast.BooleanTerm(false), nil
}
return ast.BooleanTerm(true), nil
}
func toPBChecksumAlgo(hash crypto.Hash) (gwpb.ChecksumRequest_ChecksumAlgo, error) {
switch hash {
case crypto.SHA256:
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256, nil
case crypto.SHA384:
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384, nil
case crypto.SHA512:
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512, nil
default:
return gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256, errors.Errorf("unsupported signature hash algorithm %v", hash)
}
}
func checksumAlgoMatches(algo gwpb.ChecksumRequest_ChecksumAlgo, digestAlgo digest.Algorithm) bool {
switch algo {
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA256:
return digestAlgo == digest.SHA256
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384:
return digestAlgo == digest.SHA384
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512:
return digestAlgo == digest.SHA512
default:
return false
}
}
func (p *Policy) readFile(path string, limit int64) ([]byte, error) {
if p.opt.FS == nil {
return nil, errors.Errorf("no policy FS defined for reading context files")
+183
View File
@@ -0,0 +1,183 @@
package policy
import (
"bytes"
"crypto"
"encoding/hex"
"io/fs"
"testing"
"testing/fstest"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/moby/buildkit/util/pgpsign"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/rego"
digest "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/require"
)
func TestBuiltinVerifyHTTPPGPSignatureImpl(t *testing.T) {
const (
sigPath = "sig.asc"
keyPath = "pubkey.asc"
)
payload := []byte("buildx-http-payload")
sigData, pubKeyData, checksumDigest, suffix := createDetachedPGPFixture(t, payload)
newPolicy := func(sig []byte, pub []byte) *Policy {
return NewPolicy(Opt{
FS: func() (fs.StatFS, func() error, error) {
return fstest.MapFS{
sigPath: &fstest.MapFile{Data: sig},
keyPath: &fstest.MapFile{Data: pub},
}, func() error { return nil }, nil
},
})
}
t.Run("success", func(t *testing.T) {
st := &state{
Input: Input{
HTTP: &HTTP{
checksumResponseForSignature: &httpChecksumResponseForSignature{
Digest: checksumDigest.String(),
Suffix: append([]byte(nil), suffix...),
},
},
},
}
p := newPolicy(sigData, pubKeyData)
httpVal, err := ast.InterfaceToValue(st.Input.HTTP)
require.NoError(t, err)
got, err := p.builtinVerifyHTTPPGPSignatureImpl(
rego.BuiltinContext{Context: t.Context()},
ast.NewTerm(httpVal),
ast.StringTerm(sigPath),
ast.StringTerm(keyPath),
st,
)
require.NoError(t, err)
require.Equal(t, ast.BooleanTerm(true), got)
require.Nil(t, st.checksumNeededForSignature)
})
t.Run("verify-failure-returns-false", func(t *testing.T) {
encoded := checksumDigest.Encoded()
require.NotEmpty(t, encoded)
flipped := "0" + encoded[1:]
badDigest := digest.NewDigestFromEncoded(checksumDigest.Algorithm(), flipped)
st := &state{
Input: Input{
HTTP: &HTTP{
checksumResponseForSignature: &httpChecksumResponseForSignature{
Digest: badDigest.String(),
Suffix: append([]byte(nil), suffix...),
},
},
},
}
p := newPolicy(sigData, pubKeyData)
httpVal, err := ast.InterfaceToValue(st.Input.HTTP)
require.NoError(t, err)
got, err := p.builtinVerifyHTTPPGPSignatureImpl(
rego.BuiltinContext{Context: t.Context()},
ast.NewTerm(httpVal),
ast.StringTerm(sigPath),
ast.StringTerm(keyPath),
st,
)
require.NoError(t, err)
require.Equal(t, ast.BooleanTerm(false), got)
})
t.Run("missing-checksum-response-returns-false-and-adds-unknown", func(t *testing.T) {
st := &state{
Input: Input{
HTTP: &HTTP{},
},
}
p := newPolicy(sigData, pubKeyData)
httpVal, err := ast.InterfaceToValue(st.Input.HTTP)
require.NoError(t, err)
got, err := p.builtinVerifyHTTPPGPSignatureImpl(
rego.BuiltinContext{Context: t.Context()},
ast.NewTerm(httpVal),
ast.StringTerm(sigPath),
ast.StringTerm(keyPath),
st,
)
require.NoError(t, err)
require.Equal(t, ast.BooleanTerm(false), got)
require.Contains(t, st.Unknowns, funcVerifyHTTPPGPSignature)
require.NotNil(t, st.checksumNeededForSignature)
})
t.Run("invalid-signature-errors", func(t *testing.T) {
p := newPolicy([]byte("not-a-signature"), pubKeyData)
st := &state{Input: Input{HTTP: &HTTP{}}}
httpVal, err := ast.InterfaceToValue(st.Input.HTTP)
require.NoError(t, err)
got, err := p.builtinVerifyHTTPPGPSignatureImpl(
rego.BuiltinContext{Context: t.Context()},
ast.NewTerm(httpVal),
ast.StringTerm(sigPath),
ast.StringTerm(keyPath),
st,
)
require.Nil(t, got)
require.ErrorContains(t, err, "verify_http_pgp_signature: failed to parse detached signature")
})
}
func createDetachedPGPFixture(t *testing.T, payload []byte) ([]byte, []byte, digest.Digest, []byte) {
t.Helper()
entity, err := openpgp.NewEntity("buildx", "", "buildx@example.com", &packet.Config{
DefaultHash: crypto.SHA256,
RSABits: 2048,
})
require.NoError(t, err)
var sigBuf bytes.Buffer
err = openpgp.ArmoredDetachSign(&sigBuf, entity, bytes.NewReader(payload), &packet.Config{
DefaultHash: crypto.SHA256,
})
require.NoError(t, err)
sigData := sigBuf.Bytes()
var pubBuf bytes.Buffer
aw, err := armor.Encode(&pubBuf, openpgp.PublicKeyType, nil)
require.NoError(t, err)
err = entity.Serialize(aw)
require.NoError(t, err)
err = aw.Close()
require.NoError(t, err)
pubKeyData := pubBuf.Bytes()
sig, _, err := pgpsign.ParseArmoredDetachedSignature(sigData)
require.NoError(t, err)
h := sig.Hash.New()
_, err = h.Write(payload)
require.NoError(t, err)
_, err = h.Write(sig.HashSuffix)
require.NoError(t, err)
sum := h.Sum(nil)
dAlgo := digest.SHA256
switch sig.Hash {
case crypto.SHA384:
dAlgo = digest.SHA384
case crypto.SHA512:
dAlgo = digest.SHA512
}
return sigData, pubKeyData, digest.NewDigestFromEncoded(dAlgo, hex.EncodeToString(sum)), append([]byte(nil), sig.HashSuffix...)
}
+39
View File
@@ -6,7 +6,9 @@ import (
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/moby/buildkit/client/llb/sourceresolver"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy/policysession"
"github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/require"
@@ -66,3 +68,40 @@ func TestResolveInputUnknownsResolvesMaterialField(t *testing.T) {
require.Nil(t, next)
require.True(t, inp.Image.Provenance.Materials[0].Image.HasProvenance)
}
func TestResolveInputUnknownsHTTPChecksumResponseRequest(t *testing.T) {
inp := Input{
HTTP: &HTTP{
URL: "https://example.com/file.tgz",
Schema: "https",
Host: "example.com",
Path: "/file.tgz",
Query: map[string][]string{},
},
}
checksumReq := &gwpb.ChecksumRequest{
Algo: gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384,
Suffix: []byte{0xaa, 0xbb, 0xcc},
}
p := NewPolicy(Opt{})
retry, next, err := p.resolveUnknowns(
context.Background(),
&inp,
&policysession.CheckPolicyRequest{
Source: &gwpb.ResolveSourceMetaResponse{
Source: &pb.SourceOp{Identifier: "https://example.com/file.tgz"},
},
},
nil,
nil,
&state{checksumNeededForSignature: checksumReq},
)
require.NoError(t, err)
require.False(t, retry)
require.NotNil(t, next)
require.NotNil(t, next.HTTP)
require.NotNil(t, next.HTTP.ChecksumRequest)
require.Equal(t, checksumReq.Algo, next.HTTP.ChecksumRequest.Algo)
require.Equal(t, checksumReq.Suffix, next.HTTP.ChecksumRequest.Suffix)
}
+5 -3
View File
@@ -41,11 +41,13 @@ type HTTP struct {
Checksum string `json:"checksum,omitempty"`
Signature *PGPSignature `json:"signature,omitempty"`
AttestationBundle *AttestationBundle `json:"attestationBundle,omitempty"`
checksumResponseForSignature *httpChecksumResponseForSignature `json:"-"`
}
type AttestationBundle struct{}
type httpChecksumResponseForSignature struct {
Digest string `json:"-"`
Suffix []byte `json:"-"`
}
type Git struct {
Schema string `json:"schema,omitempty"`
+4 -3
View File
@@ -111,9 +111,10 @@ func TestRuntimeUnknownInputRefs(t *testing.T) {
st := &state{
Unknowns: map[string]struct{}{
funcVerifyGitSignature: {},
funcArtifactAttestation: {},
funcGithubAttestation: {},
funcVerifyGitSignature: {},
funcArtifactAttestation: {},
funcGithubAttestation: {},
funcVerifyHTTPPGPSignature: {},
},
}
require.Equal(t, []string{"git.commit", "http.checksum"}, runtimeUnknownInputRefs(st))
+30 -4
View File
@@ -45,7 +45,8 @@ type state struct {
Input Input
Unknowns map[string]struct{}
ImagePins map[digest.Digest]struct{}
ImagePins map[digest.Digest]struct{}
checksumNeededForSignature *gwpb.ChecksumRequest
}
func (s *state) addUnknown(key string) {
@@ -274,7 +275,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
unk := collectUnknowns(pq.Support, unknowns)
unk = append(unk, runtimeUnknownInputRefs(st)...)
retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, unk)
retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, unk, st)
if err != nil {
return nil, nil, err
}
@@ -292,7 +293,7 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
return nil, nil, err
}
retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, runtimeUnknownInputRefs(st))
retry, next, err := p.resolveUnknowns(ctx, &inp, req, platform, runtimeUnknownInputRefs(st), st)
if err != nil {
return nil, nil, err
}
@@ -372,11 +373,29 @@ func (p *Policy) CheckPolicy(ctx context.Context, req *policysession.CheckPolicy
return nil, nil, errors.Errorf("maximum attempts reached for resolving policy metadata")
}
func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policysession.CheckPolicyRequest, defaultPlatform *ocispecs.Platform, unk []string) (bool, *gwpb.ResolveSourceMetaRequest, error) {
func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policysession.CheckPolicyRequest, defaultPlatform *ocispecs.Platform, unk []string, st *state) (bool, *gwpb.ResolveSourceMetaRequest, error) {
var resolver SourceMetadataResolver
if p.opt.SourceResolver != nil {
resolver = p.opt.SourceResolver
}
if st != nil && st.checksumNeededForSignature != nil {
next := &gwpb.ResolveSourceMetaRequest{
Source: req.Source.Source,
}
if req.Platform != nil {
next.Platform = req.Platform
}
if err := AddUnknownsWithLogger(p.opt.Log, next, normalizeNodeUnknowns(unk)); err != nil {
return false, nil, err
}
next.HTTP = &gwpb.ResolveSourceHTTPRequest{
ChecksumRequest: st.checksumNeededForSignature.CloneVT(),
}
p.log(logrus.InfoLevel, "policy decision for source %s: resolve missing fields %+v", sourceName(req), summarizeUnknownsForLog(unk))
return false, next, nil
}
retry, next, err := ResolveInputUnknowns(ctx, input, req.Source.Source, unk, req.Platform, defaultPlatform, resolver, p.opt.VerifierProvider, p.opt.Log)
if err != nil {
return false, nil, err
@@ -387,6 +406,7 @@ func (p *Policy) resolveUnknowns(ctx context.Context, input *Input, req *policys
}
return retry, nil, nil
}
func platformFromReq(req *policysession.CheckPolicyRequest) (*ocispecs.Platform, error) {
if req.Platform != nil {
platformStr := req.Platform.OS + "/" + req.Platform.Architecture
@@ -446,6 +466,12 @@ func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src
}
if src.HTTP != nil {
inp.HTTP.Checksum = src.HTTP.Checksum
if src.HTTP.ChecksumResponse != nil {
inp.HTTP.checksumResponseForSignature = &httpChecksumResponseForSignature{
Digest: src.HTTP.ChecksumResponse.Digest,
Suffix: slices.Clone(src.HTTP.ChecksumResponse.Suffix),
}
}
}
if inp.HTTP.Checksum == "" {
unknowns = append(unknowns, "input.http.checksum")
+25
View File
@@ -33,6 +33,14 @@ func ToResolverOpt(req *gwpb.ResolveSourceMetaRequest, defaultPlatform *ocispecs
if req != nil && req.Git != nil {
opt.GitOpt = &sourceresolver.ResolveGitOpt{ReturnObject: req.Git.ReturnObject}
}
if req != nil && req.HTTP != nil && req.HTTP.ChecksumRequest != nil {
opt.HTTPOpt = &sourceresolver.ResolveHTTPOpt{
ChecksumReq: &sourceresolver.ResolveHTTPChecksumRequest{
Algo: toResolverChecksumAlgo(req.HTTP.ChecksumRequest.Algo),
Suffix: append([]byte(nil), req.HTTP.ChecksumRequest.Suffix...),
},
}
}
return opt
}
@@ -64,10 +72,27 @@ func ToGatewayMetaResponse(resp *sourceresolver.MetaResponse) *gwpb.ResolveSourc
Filename: resp.HTTP.Filename,
LastModified: lastModified,
}
if resp.HTTP.ChecksumResponse != nil {
out.HTTP.ChecksumResponse = &gwpb.ChecksumResponse{
Digest: resp.HTTP.ChecksumResponse.Digest,
Suffix: append([]byte(nil), resp.HTTP.ChecksumResponse.Suffix...),
}
}
}
return out
}
func toResolverChecksumAlgo(in gwpb.ChecksumRequest_ChecksumAlgo) sourceresolver.ResolveHTTPChecksumAlgo {
switch in {
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA384:
return sourceresolver.ResolveHTTPChecksumAlgoSHA384
case gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512:
return sourceresolver.ResolveHTTPChecksumAlgoSHA512
default:
return sourceresolver.ResolveHTTPChecksumAlgoSHA256
}
}
func toGatewayDescriptor(desc ocispecs.Descriptor) *gwpb.Descriptor {
return &gwpb.Descriptor{
MediaType: desc.MediaType,
+43
View File
@@ -0,0 +1,43 @@
package sourcemeta
import (
"testing"
"github.com/moby/buildkit/client/llb/sourceresolver"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/solver/pb"
"github.com/stretchr/testify/require"
)
func TestToResolverOptHTTPChecksumRequest(t *testing.T) {
opt := ToResolverOpt(&gwpb.ResolveSourceMetaRequest{
Source: &pb.SourceOp{Identifier: "https://example.com/a"},
HTTP: &gwpb.ResolveSourceHTTPRequest{
ChecksumRequest: &gwpb.ChecksumRequest{
Algo: gwpb.ChecksumRequest_CHECKSUM_ALGO_SHA512,
Suffix: []byte{0x01, 0x02},
},
},
}, nil)
require.NotNil(t, opt.HTTPOpt)
require.NotNil(t, opt.HTTPOpt.ChecksumReq)
require.Equal(t, sourceresolver.ResolveHTTPChecksumAlgoSHA512, opt.HTTPOpt.ChecksumReq.Algo)
require.Equal(t, []byte{0x01, 0x02}, opt.HTTPOpt.ChecksumReq.Suffix)
}
func TestToGatewayMetaResponseHTTPChecksumResponse(t *testing.T) {
out := ToGatewayMetaResponse(&sourceresolver.MetaResponse{
Op: &pb.SourceOp{Identifier: "https://example.com/a"},
HTTP: &sourceresolver.ResolveHTTPResponse{
Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
ChecksumResponse: &sourceresolver.ResolveHTTPChecksumResponse{
Digest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
Suffix: []byte{0x03, 0x04},
},
},
})
require.NotNil(t, out.HTTP)
require.NotNil(t, out.HTTP.ChecksumResponse)
require.Equal(t, "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", out.HTTP.ChecksumResponse.Digest)
require.Equal(t, []byte{0x03, 0x04}, out.HTTP.ChecksumResponse.Suffix)
}