@@ -3,20 +3,24 @@ package policy
33import (
44 "bytes"
55 "context"
6+ "crypto"
67 "encoding/json"
78 "fmt"
89 "io"
910 "maps"
1011 "net/url"
12+ "slices"
1113 "strings"
1214
1315 "github.com/distribution/reference"
1416 "github.com/golang/snappy"
1517 slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
1618 "github.com/moby/buildkit/client/llb"
1719 gwclient "github.com/moby/buildkit/frontend/gateway/client"
20+ gwpb "github.com/moby/buildkit/frontend/gateway/pb"
1821 "github.com/moby/buildkit/solver/pb"
1922 "github.com/moby/buildkit/util/gitutil/gitsign"
23+ "github.com/moby/buildkit/util/pgpsign"
2024 "github.com/open-policy-agent/opa/v1/ast"
2125 "github.com/open-policy-agent/opa/v1/rego"
2226 "github.com/open-policy-agent/opa/v1/types"
@@ -26,11 +30,12 @@ import (
2630)
2731
2832const (
29- funcLoadJSON = "load_json"
30- funcVerifyGitSignature = "verify_git_signature"
31- funcPinImage = "pin_image"
32- funcArtifactAttestation = "artifact_attestation"
33- funcGithubAttestation = "github_attestation"
33+ funcLoadJSON = "load_json"
34+ funcVerifyGitSignature = "verify_git_signature"
35+ funcVerifyHTTPPGPSignature = "verify_http_pgp_signature"
36+ funcPinImage = "pin_image"
37+ funcArtifactAttestation = "artifact_attestation"
38+ funcGithubAttestation = "github_attestation"
3439)
3540
3641func (p * Policy ) initBuiltinFuncs () {
@@ -69,6 +74,27 @@ func (p *Policy) initBuiltinFuncs() {
6974 },
7075 })
7176
77+ verifyHTTPPGPSignature := & rego.Function {
78+ Name : funcVerifyHTTPPGPSignature ,
79+ Decl : types .NewFunction (
80+ types .Args (
81+ types .A ,
82+ types .S ,
83+ types .S ,
84+ ),
85+ types .B ,
86+ ),
87+ Memoize : false , // TODO:optimize
88+ }
89+ p .funcs = append (p .funcs , fun {
90+ decl : verifyHTTPPGPSignature ,
91+ impl : func (s * state ) func (* rego.Rego ) {
92+ return rego .Function3 (verifyHTTPPGPSignature , func (bctx rego.BuiltinContext , a1 * ast.Term , a2 * ast.Term , a3 * ast.Term ) (* ast.Term , error ) {
93+ return p .builtinVerifyHTTPPGPSignatureImpl (bctx , a1 , a2 , a3 , s )
94+ })
95+ },
96+ })
97+
7298 pinImageDigest := & rego.Function {
7399 Name : funcPinImage ,
74100 Decl : types .NewFunction (
@@ -482,6 +508,113 @@ func (p *Policy) builtinVerifyGitSignatureImpl(_ rego.BuiltinContext, a1, a2 *as
482508 return ast .BooleanTerm (true ), nil
483509}
484510
511+ func (p * Policy ) builtinVerifyHTTPPGPSignatureImpl (_ rego.BuiltinContext , a1 , a2 , a3 * ast.Term , s * state ) (* ast.Term , error ) {
512+ inp := s .Input
513+ if inp .HTTP == nil {
514+ return ast .BooleanTerm (false ), nil
515+ }
516+
517+ obja , ok := a1 .Value .(ast.Object )
518+ if ! ok {
519+ return nil , errors .Errorf ("%s: expected object, got %T" , funcVerifyHTTPPGPSignature , a1 .Value )
520+ }
521+
522+ httpValue , err := ast .InterfaceToValue (inp .HTTP )
523+ if err != nil {
524+ return nil , errors .Wrapf (err , "%s: failed converting object to interface" , funcVerifyHTTPPGPSignature )
525+ }
526+
527+ if obja .Compare (httpValue ) != 0 {
528+ return nil , errors .Errorf ("%s: first argument is not the same as input http" , funcVerifyHTTPPGPSignature )
529+ }
530+
531+ sigPath , ok := a2 .Value .(ast.String )
532+ if ! ok {
533+ return nil , errors .Errorf ("%s: expected string signature path, got %T" , funcVerifyHTTPPGPSignature , a2 .Value )
534+ }
535+ pubKeyPath , ok := a3 .Value .(ast.String )
536+ if ! ok {
537+ return nil , errors .Errorf ("%s: expected string pubkey path, got %T" , funcVerifyHTTPPGPSignature , a3 .Value )
538+ }
539+
540+ signatureData , err := p .readFile (string (sigPath ), 512 * 1024 )
541+ if err != nil {
542+ return nil , err
543+ }
544+ pubKeyData , err := p .readFile (string (pubKeyPath ), 512 * 1024 )
545+ if err != nil {
546+ return nil , err
547+ }
548+
549+ sig , _ , err := pgpsign .ParseArmoredDetachedSignature (signatureData )
550+ if err != nil {
551+ return nil , errors .Wrapf (err , "%s: failed to parse detached signature" , funcVerifyHTTPPGPSignature )
552+ }
553+ keyring , err := pgpsign .ReadAllArmoredKeyRings (pubKeyData )
554+ if err != nil {
555+ return nil , errors .Wrapf (err , "%s: failed to read armored keyring" , funcVerifyHTTPPGPSignature )
556+ }
557+
558+ algo , err := toPBChecksumAlgo (sig .Hash )
559+ if err != nil {
560+ return nil , errors .Wrapf (err , "%s: unsupported signature hash" , funcVerifyHTTPPGPSignature )
561+ }
562+ suffix := slices .Clone (sig .HashSuffix )
563+ checksumReq := & gwpb.ChecksumRequest {Algo : algo , Suffix : suffix }
564+
565+ resp := inp .HTTP .checksumResponseForSignature
566+ if resp == nil || resp .Digest == "" {
567+ s .checksumNeededForSignature = checksumReq
568+ s .addUnknown (funcVerifyHTTPPGPSignature )
569+ return ast .BooleanTerm (false ), nil
570+ }
571+ if ! bytes .Equal (resp .Suffix , suffix ) {
572+ s .checksumNeededForSignature = checksumReq
573+ s .addUnknown (funcVerifyHTTPPGPSignature )
574+ return ast .BooleanTerm (false ), nil
575+ }
576+ dgst , err := digest .Parse (resp .Digest )
577+ if err != nil {
578+ return nil , errors .Wrapf (err , "%s: invalid checksum digest" , funcVerifyHTTPPGPSignature )
579+ }
580+ if ! checksumAlgoMatches (algo , dgst .Algorithm ()) {
581+ s .checksumNeededForSignature = checksumReq
582+ s .addUnknown (funcVerifyHTTPPGPSignature )
583+ return ast .BooleanTerm (false ), nil
584+ }
585+
586+ if err := pgpsign .VerifySignatureWithDigest (sig , keyring , dgst ); err != nil {
587+ return ast .BooleanTerm (false ), nil
588+ }
589+ return ast .BooleanTerm (true ), nil
590+ }
591+
592+ func toPBChecksumAlgo (hash crypto.Hash ) (gwpb.ChecksumRequest_ChecksumAlgo , error ) {
593+ switch hash {
594+ case crypto .SHA256 :
595+ return gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA256 , nil
596+ case crypto .SHA384 :
597+ return gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA384 , nil
598+ case crypto .SHA512 :
599+ return gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA512 , nil
600+ default :
601+ return gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA256 , errors .Errorf ("unsupported signature hash algorithm %v" , hash )
602+ }
603+ }
604+
605+ func checksumAlgoMatches (algo gwpb.ChecksumRequest_ChecksumAlgo , digestAlgo digest.Algorithm ) bool {
606+ switch algo {
607+ case gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA256 :
608+ return digestAlgo == digest .SHA256
609+ case gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA384 :
610+ return digestAlgo == digest .SHA384
611+ case gwpb .ChecksumRequest_CHECKSUM_ALGO_SHA512 :
612+ return digestAlgo == digest .SHA512
613+ default :
614+ return false
615+ }
616+ }
617+
485618func (p * Policy ) readFile (path string , limit int64 ) ([]byte , error ) {
486619 if p .opt .FS == nil {
487620 return nil , errors .Errorf ("no policy FS defined for reading context files" )
0 commit comments