diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go index fd680b6f..e38b1d12 100644 --- a/cmd/chisel/cmd_cut.go +++ b/cmd/chisel/cmd_cut.go @@ -91,6 +91,7 @@ func (cmd *cmdCut) Execute(args []string) error { Suites: archiveInfo.Suites, Components: archiveInfo.Components, CacheDir: cache.DefaultDir("chisel"), + PubKeys: archiveInfo.PubKeys, }) if err != nil { return err diff --git a/go.mod b/go.mod index 18f24d37..823e559c 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,20 @@ module github.com/canonical/chisel go 1.20 require ( - github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/jessevdk/go-flags v1.5.0 github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b github.com/klauspost/compress v1.15.4 github.com/ulikunitz/xz v0.5.10 go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/term v0.12.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 ) require ( - github.com/cloudflare/circl v1.3.3 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.1.0 // indirect - golang.org/x/crypto v0.7.0 // indirect golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index ee7c440e..029e1f20 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -44,72 +39,35 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM= go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/internal/archive/archive.go b/internal/archive/archive.go index 63badc8f..2a552084 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -8,9 +8,12 @@ import ( "strings" "time" + "golang.org/x/crypto/openpgp/packet" + "github.com/canonical/chisel/internal/cache" "github.com/canonical/chisel/internal/control" "github.com/canonical/chisel/internal/deb" + "github.com/canonical/chisel/internal/pgputil" ) type Archive interface { @@ -26,6 +29,7 @@ type Options struct { Suites []string Components []string CacheDir string + PubKeys []*packet.PublicKey } func Open(options *Options) (Archive, error) { @@ -64,6 +68,7 @@ type ubuntuArchive struct { options Options indexes []*ubuntuIndex cache *cache.Cache + pubKeys []*packet.PublicKey } type ubuntuIndex struct { @@ -74,7 +79,7 @@ type ubuntuIndex struct { component string release control.Section packages control.File - cache *cache.Cache + archive *ubuntuArchive } func (a *ubuntuArchive) Options() *Options { @@ -140,6 +145,7 @@ func openUbuntu(options *Options) (Archive, error) { cache: &cache.Cache{ Dir: options.CacheDir, }, + pubKeys: options.PubKeys, } for _, suite := range options.Suites { @@ -152,7 +158,7 @@ func openUbuntu(options *Options) (Archive, error) { suite: suite, component: component, release: release, - cache: archive.cache, + archive: archive, } if release == nil { err := index.fetchRelease() @@ -178,20 +184,44 @@ func openUbuntu(options *Options) (Archive, error) { func (index *ubuntuIndex) fetchRelease() error { logf("Fetching %s %s %s suite details...", index.label, index.version, index.suite) - reader, err := index.fetch("Release", "", fetchDefault) + reader, err := index.fetch("InRelease", "", fetchDefault) + if err != nil { + return err + } + defer reader.Close() + + data, err := io.ReadAll(reader) if err != nil { return err } - ctrl, err := control.ParseReader("Label", reader) + // Decode the signature(s) and verify the InRelease file. The InRelease + // file may have multiple signatures from different keys. Verify that at + // least one signature is valid against the archive's set of public keys. + // Unlike gpg --verify which ensures the verification of all signatures, + // this is in line with what apt does internally: + // https://salsa.debian.org/apt-team/apt/-/blob/4e344a4/methods/gpgv.cc#L553-557 + sigs, canonicalBody, err := pgputil.DecodeClearSigned(data) + if err != nil { + return fmt.Errorf("cannot decode clearsigned InRelease file: %v", err) + } + err = pgputil.VerifyAnySignature(index.archive.pubKeys, sigs, canonicalBody) + if err != nil { + return fmt.Errorf("cannot verify signature of the InRelease file") + } + + // canonicalBody has line endings, reverting that to match the + // expected control file format. + body := strings.ReplaceAll(string(canonicalBody), "\r", "") + ctrl, err := control.ParseString("Label", body) if err != nil { - return fmt.Errorf("parsing archive Release file: %v", err) + return fmt.Errorf("cannot parse InRelease file: %v", err) } section := ctrl.Section("Ubuntu") if section == nil { section = ctrl.Section("UbuntuProFIPS") if section == nil { - return fmt.Errorf("corrupted archive Release file: no Ubuntu section") + return fmt.Errorf("corrupted archive InRelease file: no Ubuntu section") } } logf("Release date: %s", section.Get("Date")) @@ -240,7 +270,7 @@ func (index *ubuntuIndex) checkComponents(components []string) error { } func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.ReadCloser, error) { - reader, err := index.cache.Open(digest) + reader, err := index.archive.cache.Open(digest) if err == nil { return reader, nil } else if err != cache.MissErr { @@ -293,7 +323,7 @@ func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.Rea body = reader } - writer := index.cache.Create(digest) + writer := index.archive.cache.Create(digest) defer writer.Close() _, err = io.Copy(writer, body) @@ -304,5 +334,5 @@ func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.Rea return nil, fmt.Errorf("cannot fetch from archive: %v", err) } - return index.cache.Open(writer.Digest()) + return index.archive.cache.Open(writer.Digest()) } diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index cb9f3278..af741dbe 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -1,6 +1,7 @@ package archive_test import ( + "golang.org/x/crypto/openpgp/packet" . "gopkg.in/check.v1" "debug/elf" @@ -18,6 +19,7 @@ import ( "github.com/canonical/chisel/internal/archive" "github.com/canonical/chisel/internal/archive/testarchive" "github.com/canonical/chisel/internal/deb" + "github.com/canonical/chisel/internal/testutil" ) type httpSuite struct { @@ -31,10 +33,18 @@ type httpSuite struct { header http.Header status int restore func() + privKey *packet.PrivateKey + pubKey *packet.PublicKey } var _ = Suite(&httpSuite{}) +var ( + key1 = testutil.PGPKeys["key1"] + key2 = testutil.PGPKeys["key2"] + keyUbuntu2018 = testutil.PGPKeys["key-ubuntu-2018"] +) + func (s *httpSuite) SetUpTest(c *C) { s.logf = c.Logf s.err = nil @@ -46,6 +56,8 @@ func (s *httpSuite) SetUpTest(c *C) { s.header = nil s.status = 200 s.restore = archive.FakeDo(s.Do) + s.privKey = key1.PrivKey + s.pubKey = key1.PubKey } func (s *httpSuite) TearDownTest(c *C) { @@ -97,6 +109,7 @@ func (s *httpSuite) prepareArchiveAdjustRelease(suite, version, arch string, com Suite: suite, Version: version, Label: "Ubuntu", + PrivKey: s.privKey, } for i, component := range components { index := &testarchive.PackageIndex{ @@ -172,6 +185,7 @@ func (s *httpSuite) TestOptionErrors(c *C) { cacheDir := c.MkDir() for _, test := range optionErrorTests { test.options.CacheDir = cacheDir + test.options.PubKeys = append(test.options.PubKeys, s.pubKey) _, err := archive.Open(&test.options) c.Assert(err, ErrorMatches, test.error) } @@ -188,6 +202,7 @@ func (s *httpSuite) TestFetchPackage(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } archive, err := archive.Open(&options) @@ -217,6 +232,7 @@ func (s *httpSuite) TestFetchPortsPackage(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } archive, err := archive.Open(&options) @@ -254,6 +270,7 @@ func (s *httpSuite) TestFetchSecurityPackage(c *C) { Arch: "amd64", Suites: []string{"jammy", "jammy-security", "jammy-updates"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{s.pubKey}, } archive, err := archive.Open(&options) @@ -284,6 +301,7 @@ func (s *httpSuite) TestArchiveLabels(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } _, err := archive.Open(&options) @@ -298,6 +316,7 @@ func (s *httpSuite) TestArchiveLabels(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } _, err = archive.Open(&options) @@ -312,6 +331,7 @@ func (s *httpSuite) TestArchiveLabels(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } _, err = archive.Open(&options) @@ -326,12 +346,59 @@ func (s *httpSuite) TestArchiveLabels(c *C) { Suites: []string{"jammy"}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, } _, err = archive.Open(&options) c.Assert(err, ErrorMatches, `.*\bno Ubuntu section`) } +type verifyArchiveReleaseTest struct { + summary string + pubKeys []*packet.PublicKey + error string +} + +var verifyArchiveReleaseTests = []verifyArchiveReleaseTest{{ + summary: "A valid public key", + pubKeys: []*packet.PublicKey{key1.PubKey}, +}, { + summary: "No public key to verify with", + error: `cannot verify signature of the InRelease file`, +}, { + summary: "Wrong public key", + pubKeys: []*packet.PublicKey{key2.PubKey}, + error: `cannot verify signature of the InRelease file`, +}, { + summary: "Multiple public keys (invalid, valid)", + pubKeys: []*packet.PublicKey{key2.PubKey, key1.PubKey}, +}} + +func (s *httpSuite) TestVerifyArchiveRelease(c *C) { + for _, test := range verifyArchiveReleaseTests { + c.Logf("Summary: %s", test.summary) + + s.prepareArchive("jammy", "22.04", "amd64", []string{"main", "universe"}) + + options := archive.Options{ + Label: "ubuntu", + Version: "22.04", + Arch: "amd64", + Suites: []string{"jammy"}, + Components: []string{"main", "universe"}, + CacheDir: c.MkDir(), + PubKeys: test.pubKeys, + } + + _, err := archive.Open(&options) + if test.error != "" { + c.Assert(err, ErrorMatches, test.error) + } else { + c.Assert(err, IsNil) + } + } +} + func read(r io.Reader) string { data, err := io.ReadAll(r) if err != nil { @@ -349,11 +416,39 @@ func (s *S) TestRealArchive(c *C) { if !*realArchiveFlag { c.Skip("--real-archive not provided") } - for _, arch := range elfToDebArch { - s.testOpenArchiveArch(c, arch) + for _, release := range ubuntuReleases { + for _, arch := range elfToDebArch { + s.testOpenArchiveArch(c, release, arch) + } } } +type ubuntuRelease struct { + name string + version string + archivePubKeys []*packet.PublicKey +} + +var ubuntuReleases = []ubuntuRelease{{ + name: "focal", + version: "20.04", + archivePubKeys: []*packet.PublicKey{ + keyUbuntu2018.PubKey, + }, +}, { + name: "jammy", + version: "22.04", + archivePubKeys: []*packet.PublicKey{ + keyUbuntu2018.PubKey, + }, +}, { + name: "noble", + version: "24.04", + archivePubKeys: []*packet.PublicKey{ + keyUbuntu2018.PubKey, + }, +}} + var elfToDebArch = map[elf.Machine]string{ elf.EM_386: "i386", elf.EM_AARCH64: "arm64", @@ -373,14 +468,17 @@ func (s *S) checkArchitecture(c *C, arch string, binaryPath string) { c.Assert(binaryArch, Equals, arch) } -func (s *S) testOpenArchiveArch(c *C, arch string) { +func (s *S) testOpenArchiveArch(c *C, release ubuntuRelease, arch string) { + c.Logf("Checking ubuntu archive %s %s...", release.name, arch) + options := archive.Options{ Label: "ubuntu", - Version: "22.04", + Version: release.version, Arch: arch, - Suites: []string{"jammy"}, + Suites: []string{release.name}, Components: []string{"main", "universe"}, CacheDir: c.MkDir(), + PubKeys: release.archivePubKeys, } archive, err := archive.Open(&options) @@ -408,8 +506,8 @@ func (s *S) testOpenArchiveArch(c *C, arch string) { data, err := os.ReadFile(filepath.Join(extractDir, "copyright")) c.Assert(err, IsNil) - copyrightTop := "This package was written by Peter Tobias \non Thu, 16 Jan 1997 01:00:34 +0100." - c.Assert(strings.HasPrefix(string(data), copyrightTop), Equals, true) + copyrightTop := "This package was written by Peter Tobias " + c.Assert(strings.Contains(string(data), copyrightTop), Equals, true) s.checkArchitecture(c, arch, filepath.Join(extractDir, "hostname")) } diff --git a/internal/archive/testarchive/testarchive.go b/internal/archive/testarchive/testarchive.go index dd2ee89f..f51f0977 100644 --- a/internal/archive/testarchive/testarchive.go +++ b/internal/archive/testarchive/testarchive.go @@ -8,6 +8,9 @@ import ( "path" "strings" + "golang.org/x/crypto/openpgp/clearsign" + "golang.org/x/crypto/openpgp/packet" + "github.com/canonical/chisel/internal/testutil" ) @@ -103,6 +106,7 @@ type Release struct { Version string Label string Items []Item + PrivKey *packet.PrivateKey } func (r *Release) Walk(f func(Item) error) error { @@ -110,7 +114,7 @@ func (r *Release) Walk(f func(Item) error) error { } func (r *Release) Path() string { - return "Release" + return "InRelease" } func (r *Release) Section() []byte { @@ -137,7 +141,20 @@ func (r *Release) Content() []byte { %s `)), r.Label, r.Suite, r.Version, r.Version, digests.String()) - return []byte(content) + var buf bytes.Buffer + writer, err := clearsign.Encode(&buf, r.PrivKey, nil) + if err != nil { + panic(err) + } + _, err = writer.Write([]byte(content)) + if err != nil { + panic(err) + } + err = writer.Close() + if err != nil { + panic(err) + } + return buf.Bytes() } func (r *Release) Render(prefix string, content map[string][]byte) error { diff --git a/internal/pgputil/log.go b/internal/pgputil/log.go new file mode 100644 index 00000000..20793699 --- /dev/null +++ b/internal/pgputil/log.go @@ -0,0 +1,53 @@ +package pgputil + +import ( + "fmt" + "sync" +) + +// Avoid importing the log type information unnecessarily. There's a small cost +// associated with using an interface rather than the type. Depending on how +// often the logger is plugged in, it would be worth using the type instead. +type log_Logger interface { + Output(calldepth int, s string) error +} + +var globalLoggerLock sync.Mutex +var globalLogger log_Logger +var globalDebug bool + +// Specify the *log.Logger object where log messages should be sent to. +func SetLogger(logger log_Logger) { + globalLoggerLock.Lock() + globalLogger = logger + globalLoggerLock.Unlock() +} + +// Enable the delivery of debug messages to the logger. Only meaningful +// if a logger is also set. +func SetDebug(debug bool) { + globalLoggerLock.Lock() + globalDebug = debug + globalLoggerLock.Unlock() +} + +// logf sends to the logger registered via SetLogger the string resulting +// from running format and args through Sprintf. +func logf(format string, args ...interface{}) { + globalLoggerLock.Lock() + defer globalLoggerLock.Unlock() + if globalLogger != nil { + globalLogger.Output(2, fmt.Sprintf(format, args...)) + } +} + +// debugf sends to the logger registered via SetLogger the string resulting +// from running format and args through Sprintf, but only if debugging was +// enabled via SetDebug. +func debugf(format string, args ...interface{}) { + globalLoggerLock.Lock() + defer globalLoggerLock.Unlock() + if globalDebug && globalLogger != nil { + globalLogger.Output(2, fmt.Sprintf(format, args...)) + } +} diff --git a/internal/pgputil/openpgp.go b/internal/pgputil/openpgp.go new file mode 100644 index 00000000..dfa013bb --- /dev/null +++ b/internal/pgputil/openpgp.go @@ -0,0 +1,113 @@ +package pgputil + +import ( + "bytes" + "fmt" + "io" + + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/clearsign" + "golang.org/x/crypto/openpgp/packet" +) + +// DecodeKeys decodes public and private key packets from armored data. +func DecodeKeys(armoredData []byte) (pubKeys []*packet.PublicKey, privKeys []*packet.PrivateKey, err error) { + block, err := armor.Decode(bytes.NewReader(armoredData)) + if err != nil { + return nil, nil, fmt.Errorf("cannot decode armored data") + } + + reader := packet.NewReader(block.Body) + for { + p, err := reader.Next() + if err != nil { + if err == io.EOF { + break + } + return nil, nil, err + } + if privKey, ok := p.(*packet.PrivateKey); ok { + privKeys = append(privKeys, privKey) + } + if pubKey, ok := p.(*packet.PublicKey); ok { + pubKeys = append(pubKeys, pubKey) + } + } + return pubKeys, privKeys, nil +} + +// DecodePubKey decodes a single public key packet from armored data. The +// data should contain exactly one public key packet and no private key packets. +func DecodePubKey(armoredData []byte) (*packet.PublicKey, error) { + pubKeys, privKeys, err := DecodeKeys(armoredData) + if err != nil { + return nil, err + } + if len(privKeys) > 0 { + return nil, fmt.Errorf("armored data contains private key") + } + if len(pubKeys) > 1 { + return nil, fmt.Errorf("armored data contains more than one public key") + } + if len(pubKeys) == 0 { + return nil, fmt.Errorf("armored data contains no public key") + } + return pubKeys[0], nil +} + +// DecodeClearSigned decodes the first clearsigned message in the data and +// returns the signatures and the message body. +// +// The returned canonicalBody is canonicalized by converting line endings to +// per the openPGP RCF: https://www.rfc-editor.org/rfc/rfc4880#section-5.2.4 +func DecodeClearSigned(clearData []byte) (sigs []*packet.Signature, canonicalBody []byte, err error) { + block, _ := clearsign.Decode(clearData) + if block == nil { + return nil, nil, fmt.Errorf("cannot decode clearsign text") + } + reader := packet.NewReader(block.ArmoredSignature.Body) + for { + p, err := reader.Next() + if err != nil { + if err == io.EOF { + break + } + return nil, nil, fmt.Errorf("cannot parse armored data: %w", err) + } + if sig, ok := p.(*packet.Signature); ok { + sigs = append(sigs, sig) + } + } + if len(sigs) == 0 { + return nil, nil, fmt.Errorf("clearsigned data contains no signatures") + } + return sigs, block.Bytes, nil +} + +// VerifySignature returns nil if sig is a valid signature from pubKey. +func VerifySignature(pubKey *packet.PublicKey, sig *packet.Signature, body []byte) error { + hash := sig.Hash.New() + _, err := io.Copy(hash, bytes.NewBuffer(body)) + if err != nil { + return err + } + return pubKey.VerifySignature(hash, sig) +} + +// VerifyAnySignature returns nil if any signature in sigs is a valid signature +// mady by any of the public keys in pubKeys. +func VerifyAnySignature(pubKeys []*packet.PublicKey, sigs []*packet.Signature, body []byte) error { + var err error + for _, sig := range sigs { + for _, key := range pubKeys { + err = VerifySignature(key, sig, body) + if err == nil { + return nil + } + } + } + if len(sigs) == 1 && len(pubKeys) == 1 { + return err + } + return fmt.Errorf("cannot verify any signatures") +} diff --git a/internal/pgputil/openpgp_test.go b/internal/pgputil/openpgp_test.go new file mode 100644 index 00000000..cec96300 --- /dev/null +++ b/internal/pgputil/openpgp_test.go @@ -0,0 +1,267 @@ +package pgputil_test + +import ( + "golang.org/x/crypto/openpgp/packet" + . "gopkg.in/check.v1" + + "github.com/canonical/chisel/internal/pgputil" + "github.com/canonical/chisel/internal/testutil" +) + +var ( + key1 = testutil.PGPKeys["key1"] + key2 = testutil.PGPKeys["key2"] +) + +type archiveKeyTest struct { + summary string + armor string + relerror string + pubKey *packet.PublicKey +} + +var archiveKeyTests = []archiveKeyTest{{ + summary: "Armored data with one public key", + armor: key1.PubKeyArmor, + pubKey: key1.PubKey, +}, { + summary: "Armored data with two public keys", + armor: twoPubKeysArmor, + relerror: "armored data contains more than one public key", +}, { + summary: "Armored data with no public key", + armor: armoredDataWithNoKeys, + relerror: "armored data contains no public key", +}, { + summary: "Armored data with private key", + armor: key1.PrivKeyArmor, + relerror: "armored data contains private key", +}, { + summary: "Invalid armored data", + armor: ` + Roses are red + Violets are blue + `, + relerror: "cannot decode armored data", +}, { + summary: "Empty armored data", + relerror: "cannot decode armored data", +}, { + summary: "Armored data: bad packets", + armor: invalidPubKeyArmor, + relerror: "openpgp: .*", +}} + +func (s *S) TestDecodeArchivePubKey(c *C) { + for _, test := range archiveKeyTests { + c.Logf("Summary: %s", test.summary) + + pubKey, err := pgputil.DecodePubKey([]byte(test.armor)) + if test.relerror != "" { + c.Assert(err, ErrorMatches, test.relerror) + continue + } + c.Assert(err, IsNil) + + c.Assert(pubKey, DeepEquals, test.pubKey) + } +} + +type verifyClearSignTest struct { + summary string + clearData string + pubKeys []*packet.PublicKey + relerror string +} + +var verifyClearSignTests = []verifyClearSignTest{{ + summary: "Good data with proper sign", + clearData: clearSignedData, + pubKeys: []*packet.PublicKey{key1.PubKey}, +}, { + summary: "Good data with multiple signatures", + clearData: clearSignedWithMultipleSigns, + pubKeys: []*packet.PublicKey{key1.PubKey, key2.PubKey}, +}, { + summary: "Multiple signatures: verify at least one signature", + clearData: clearSignedWithMultipleSigns, + pubKeys: []*packet.PublicKey{key1.PubKey}, +}, { + summary: "Multiple signatures: no valid public keys", + clearData: clearSignedWithMultipleSigns, + relerror: "cannot verify any signatures", +}, { + summary: "Invalid data: improper hash", + clearData: invalidClearSignedData, + pubKeys: []*packet.PublicKey{key1.PubKey}, + relerror: "openpgp: .*invalid signature: hash tag doesn't match.*", +}, { + summary: "Invalid data: bad packets", + clearData: invalidClearSignedDataBadPackets, + pubKeys: []*packet.PublicKey{key1.PubKey}, + relerror: "cannot parse armored data: openpgp: .*", +}, { + summary: "Invalid data: malformed clearsign text", + clearData: "foo\n", + pubKeys: []*packet.PublicKey{key1.PubKey}, + relerror: "cannot decode clearsign text", +}, { + summary: "Wrong public key to verify with", + clearData: clearSignedData, + pubKeys: []*packet.PublicKey{key2.PubKey}, + relerror: "openpgp: .*invalid signature:.*verification failure", +}} + +func (s *S) TestVerifySignature(c *C) { + for _, test := range verifyClearSignTests { + c.Logf("Summary: %s", test.summary) + + sigs, body, err := pgputil.DecodeClearSigned([]byte(test.clearData)) + if err == nil { + err = pgputil.VerifyAnySignature(test.pubKeys, sigs, body) + } + if test.relerror != "" { + c.Assert(err, ErrorMatches, test.relerror) + continue + } else { + c.Assert(err, IsNil) + } + } +} + +// twoPubKeysArmor contains two public keys: +// - 854BAF1AA9D76600 ("foo-bar ") +// - 871920D1991BC93C ("Ubuntu Archive Automatic Signing Key (2018) ") +const twoPubKeysArmor = ` +-----BEGIN PGP ARMORED FILE----- + +mQENBGVs8P4BCADPh/fNnw2AI1JCYf+3p4jkcFQPLVsUkoTZk8OXjCxy+UP9Jd2m +xnxat7a0JEJZa0aWCmtlSL1XR+kFKBrd7Ry5jOHYjuDKx4kTmDUbezPnjoZIGDNX +j5cdNuMLpOINZweNNWDKRdRvhj5QX89/DYwPrLkNFwwjXjlj5tjU6RUkROYJBGPe +G2ns2cZtVbYMh3FDU9YRfp/hUqGVf+UFRyUw+mo1TUlk5F7fnfwEQmsppDHvfTNJ +yjEMZD7nReTEeMy12GV2wysOwWMPEb2PSE/+Od7AKn5dFA7w3kyLCzAxYp6o7IE/ ++RY8YzAJe6GmLwhTWtylMV1xteQhZkEe/QGXABEBAAGZAg0EW5/B2gEQAO/8bHK3 +H8txJdi4zQzAqiNsHJ7zWzQbWcTH6XPpUBSkhRmduSp1cEcL53CsZL8J54u4CM9E +tTwCjET+OO9lWnzEUYRYdh2SWpcZn+Al8/l3d8hQG1kdkQmXwHyb2kwd/8BBB2wL +5jOLNIbm3kyGei3DTjgte10QSTHa3onPQ4auH7kijGpfulmKroK/X0GiFpSKgox2 +nsRLpFh83uiXodIsWWsxe1V+H+KOk32PdmFUZV5ELyQodCwnk+Qhua/EGJSHtImZ +9lTHQhCE0xoMdd91kAY22eHPM1F5vUWo0tJWStL8+ewBDMyEbUEObZU5IXriN5sp +d98WozktdFBN6pMuyNRtuupHqz8YI7xQXuN9SPoju1ovKCawc78kPiOkpELSBulQ +F9qInIu+56nHeRaiovew3QuGUwjzT58DsZO+g7Hi2mpWXOUTpNqNi76N9bdCk4VL +l7AQx0vbqHPGxmD+B5m9NsCtw/46wkpGaG/iQ2joDJ3IdD/dlX9/df2ZPf9I8tsl +q6aSCndjN3q3k94G75lCT+Y3lY025qKE0RXuWVvVmG9jQXG70FV38E2XSvO7Gnes +iKcHZNfZIKDvATnFeTBe5D/Z5MMTS/QeUae2S5mMajANmTEdlBLFlU7N1kJFVpf9 +YQUukprYBCnDlEmtDihn85+J9fInM/bujTfBABEBAAE= +=Znwy +-----END PGP ARMORED FILE----- +` + +// clearSignedData is signed with key 854BAF1AA9D76600 (foo@bar). +const clearSignedData = ` +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +foo +-----BEGIN PGP SIGNATURE----- + +iQE8BAEBCgAmFiEEDp0LAdsRnT9gfhU5hUuvGqnXZgAFAmVuylQIHGZvb0BiYXIA +CgkQhUuvGqnXZgDB5wf/UaxTLwO22BQdpjtkRWoI9EooNr02K5jW7x4Y73akuBFt +EJi1bUPrNKFqL7VDTMiaRv+1RSytY9U3+AKgMKVq1p7Iwr2t6CLs3D7bqw9Vy2Z4 +SpjS8zZQ5H+7t0O2zqNSu4UqBTCXWIsW9EiL1EHr92F2O3HhOn1ER7KgTl+GDUZ/ +4szrBZsfltvX51UMvFD1TO9EYcJ4tzB6mvftTBZZ6KeoyUC5u4a1ZljYkujWAlFW +VvD4PlSNTcSmpZTICEmLmb3DLlXezQ0Rgfwy6Q6X0kt9xztIJsNo5sgRxQUlpVl3 +5VFsefx4LxtZvdSFK0SNh7UAhdOzD5Tc/7aG0NFfjw== +=BAhz +-----END PGP SIGNATURE----- +` + +// This data contains two signatures, from these keys: +// - 854BAF1AA9D76600 (foo@bar) +// - 9568570379BF1F43 (test@key) +const clearSignedWithMultipleSigns = ` +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +foo +-----BEGIN PGP SIGNATURE----- + +iQEzBAEBCgAdFiEEDp0LAdsRnT9gfhU5hUuvGqnXZgAFAmVxmacACgkQhUuvGqnX +ZgBarggAp4YWGia+9yUJG3ojieaSFnue9Ov/YnhV3PLKqxo1+DJZZDekxuxk7sbU +x+ZQmM/3xus1MhEVmySvwEiuGktr9fk+/eEZOZj6d4ZTTloUeDZNaJ7LSUEUKdMM +HA5Adphtv+vBZwmkH6u7jyJSGC+P/U7DFmIPODeDcqLzh5hjWWK1dkNqkwEF75Ot +9AXI5Y0e4WWJj/UQ1zuUwtw9Rf4JB8MUFOVUPJe4UFZw+XUYHq5DFBNYLn2SDLMQ +BQ3hzmDE9FazILBIFfutKTpA3gmPu9wZ+WroNXkKkleV0Wjo0kA4bnz5hLy2D4Bf +DBATaX5qzUwC9LxpzNJoScsW/2U+KYizBAEBCgAdFiEEVCsygCgPC1R8H4YGlWhX +A3m/H0MFAmVxmacACgkQlWhXA3m/H0PVCgQArXUt7hQO3bATZBsbTgQ2INhs1aiR +GAWkroW5Dp5mOmTtAtfFuysEMdH+v42Z6g1BqwypWtCVNYF+v8aQYwUwUulN/Pna +qtWNWLmXMFLmNVILL9X+o/sRCtra1qCu6Vn59H+yPhye9CXiV+U/V8dB60YLs812 +cgcXWByCFx3J1hM= +=1GLl +-----END PGP SIGNATURE----- +` + +// This should be an invalid clearsign data. Obtained by changing +// "foo" to "bar" in clearSignedData defined above. +const invalidClearSignedData = ` +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +bar +-----BEGIN PGP SIGNATURE----- + +iQE8BAEBCgAmFiEEDp0LAdsRnT9gfhU5hUuvGqnXZgAFAmVuylQIHGZvb0BiYXIA +CgkQhUuvGqnXZgDB5wf/UaxTLwO22BQdpjtkRWoI9EooNr02K5jW7x4Y73akuBFt +EJi1bUPrNKFqL7VDTMiaRv+1RSytY9U3+AKgMKVq1p7Iwr2t6CLs3D7bqw9Vy2Z4 +SpjS8zZQ5H+7t0O2zqNSu4UqBTCXWIsW9EiL1EHr92F2O3HhOn1ER7KgTl+GDUZ/ +4szrBZsfltvX51UMvFD1TO9EYcJ4tzB6mvftTBZZ6KeoyUC5u4a1ZljYkujWAlFW +VvD4PlSNTcSmpZTICEmLmb3DLlXezQ0Rgfwy6Q6X0kt9xztIJsNo5sgRxQUlpVl3 +5VFsefx4LxtZvdSFK0SNh7UAhdOzD5Tc/7aG0NFfjw== +=BAhz +-----END PGP SIGNATURE----- +` + +// This should be an invalid clearsign data with invalid packets. +// Obtained by removing some lines from clearSignedData above. +const invalidClearSignedDataBadPackets = ` +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +foo +-----BEGIN PGP SIGNATURE----- + +qtWNWLmXMFLmNVILL9X+o/sRCtra1qCu6Vn59H+yPhye9CXiV+U/V8dB60YLs812 +cgcXWByCFx3J1hM= +=1GLl +-----END PGP SIGNATURE----- +` + +// armoredDataWithNoKeys contains only a signature packet, to be +// used for testing purposes. It does not contain any key packets. +const armoredDataWithNoKeys = ` +-----BEGIN PGP ARMORED FILE----- +Comment: Use "gpg --dearmor" for unpacking + +iQI4BBMBCgAiBQJbn8HaAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCH +GSDRmRvJPCxzEACktnJ8c/+VmqAjlgK3+YOlB23jgoHOQwZtIQrhQ2Vlr+Nu2hno +twj7i8NAxiwl2XcnOXahPJr4zJTppgCipY9bhoN02Am0Fo1j3jJwT2W5BYJGaFye +/+gge21kYbdbB86bdS02fkmA8DsCevEEaew0WmZfWOkIlG3roatg1HE6H1WwcW4a +3JDeGbXi75vv5xvZv3IqKXOui8EXZManyd9gsqvtU0uVWiCQxuw1s4hvim7uqggz +OEDZYNyx+6deAq0cQG3OJb6IUYLFeHkKrCHHRZLlWORzz49ivE6qWOkk3vBodGqa +xtUVfGSmstykjKZ8ldXwCp+HzPW8oi80AKLwtC2fTDDLKwEv+OQLwtyBCkkoYyxZ +9V9XUQojuv+45mRKGbQKed4ZH/EjAbIu/IVTawbpmcHyHQQNb9tvi2OMUCvKuFwq +EXAPRvqb81PWFVu3EZw2WRpdLsDsO8/T5EAReShSo1g8+HwpPiuvmLRqaLxinpBg +W/COxAOlKbz4KgP0HSNLdSAT9DdOkUHLNX1GgEBLc+gxsuc5EYUeKRkmZ/nRRE+z +3QIxCvOMuwXWOLflNY3EiLwY9Bdgey8ES+8RqUqSCov3pAFy7Nde27xR2gr5lGDe +yVadRjJlRcYSHceghZt38RvEIzW+bXq3v2KivrjoHF58tVJcLQlM5a0mjw== +=cp5f +-----END PGP ARMORED FILE----- +` + +// invalidPubKeyArmor contains bad packets. +const invalidPubKeyArmor = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EZXAwcgEEAMBQ4Qx6xam1k1hyjPrKQfCnGRBBm2+Lw9DHQcz0lreH51iZEVkS +=U79/ +-----END PGP PUBLIC KEY BLOCK----- +` diff --git a/internal/pgputil/suite_test.go b/internal/pgputil/suite_test.go new file mode 100644 index 00000000..1fa96dbd --- /dev/null +++ b/internal/pgputil/suite_test.go @@ -0,0 +1,25 @@ +package pgputil_test + +import ( + "testing" + + . "gopkg.in/check.v1" + + "github.com/canonical/chisel/internal/pgputil" +) + +func Test(t *testing.T) { TestingT(t) } + +type S struct{} + +var _ = Suite(&S{}) + +func (s *S) SetUpTest(c *C) { + pgputil.SetDebug(true) + pgputil.SetLogger(c) +} + +func (s *S) TearDownTest(c *C) { + pgputil.SetDebug(false) + pgputil.SetLogger(nil) +} diff --git a/internal/setup/setup.go b/internal/setup/setup.go index 7840d406..e04f8eca 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -9,10 +9,11 @@ import ( "regexp" "strings" - "github.com/ProtonMail/go-crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" "gopkg.in/yaml.v3" "github.com/canonical/chisel/internal/deb" + "github.com/canonical/chisel/internal/pgputil" "github.com/canonical/chisel/internal/strdist" ) @@ -31,7 +32,7 @@ type Archive struct { Version string Suites []string Components []string - Keyrings []openpgp.KeyRing + PubKeys []*packet.PublicKey } // Package holds a collection of slices that represent parts of themselves. @@ -320,7 +321,7 @@ func readSlices(release *Release, baseDir, dirName string) error { type yamlRelease struct { Format string `yaml:"format"` Archives map[string]yamlArchive `yaml:"archives"` - Keyrings map[string]string `yaml:"public-keys"` + PubKeys map[string]yamlPubKey `yaml:"v1-public-keys"` } const yamlReleaseFormat = "chisel-v1" @@ -330,7 +331,7 @@ type yamlArchive struct { Suites []string `yaml:"suites"` Components []string `yaml:"components"` Default bool `yaml:"default"` - Keyrings []string `yaml:"public-keys"` + PubKeys []string `yaml:"v1-public-keys"` } type yamlPackage struct { @@ -388,6 +389,11 @@ type yamlSlice struct { Mutate string `yaml:"mutate"` } +type yamlPubKey struct { + ID string `yaml:"id"` + Armor string `yaml:"armor"` +} + var ubuntuAdjectives = map[string]string{ "18.04": "bionic", "20.04": "focal", @@ -418,14 +424,17 @@ func parseRelease(baseDir, filePath string, data []byte) (*Release, error) { return nil, fmt.Errorf("%s: no archives defined", fileName) } - keyringsByName := make(map[string]openpgp.KeyRing, 0) - - for keyringName, keyringArmored := range yamlVar.Keyrings { - keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyringArmored)) + // Decode the public keys and match against provided IDs. + pubKeys := make(map[string]*packet.PublicKey, len(yamlVar.PubKeys)) + for keyName, yamlPubKey := range yamlVar.PubKeys { + key, err := pgputil.DecodePubKey([]byte(yamlPubKey.Armor)) if err != nil { - return nil, fmt.Errorf("%s: cannot parse keyring %q: %w", fileName, keyringName, err) + return nil, fmt.Errorf("%s: cannot decode public key %q: %w", fileName, keyName, err) } - keyringsByName[keyringName] = keyring + if yamlPubKey.ID != key.KeyIdString() { + return nil, fmt.Errorf("%s: public key %q armor has incorrect ID: expected %q, got %q", fileName, keyName, yamlPubKey.ID, key.KeyIdString()) + } + pubKeys[keyName] = key } for archiveName, details := range yamlVar.Archives { @@ -442,14 +451,6 @@ func parseRelease(baseDir, filePath string, data []byte) (*Release, error) { if len(details.Components) == 0 { return nil, fmt.Errorf("%s: archive %q missing components field", fileName, archiveName) } - var archiveKeyrings []openpgp.KeyRing - for _, keyringName := range details.Keyrings { - keyring := keyringsByName[keyringName] - if keyring == nil { - return nil, fmt.Errorf("%s: archive %q references unknown keyring %q", fileName, archiveName, keyringName) - } - archiveKeyrings = append(archiveKeyrings, keyring) - } if len(yamlVar.Archives) == 1 { details.Default = true } else if details.Default && release.DefaultArchive != "" { @@ -458,12 +459,23 @@ func parseRelease(baseDir, filePath string, data []byte) (*Release, error) { if details.Default { release.DefaultArchive = archiveName } + if len(details.PubKeys) == 0 { + return nil, fmt.Errorf("%s: archive %q missing v1-public-keys field", fileName, archiveName) + } + var archiveKeys []*packet.PublicKey + for _, keyName := range details.PubKeys { + key, ok := pubKeys[keyName] + if !ok { + return nil, fmt.Errorf("%s: archive %q refers to undefined public key %q", fileName, archiveName, keyName) + } + archiveKeys = append(archiveKeys, key) + } release.Archives[archiveName] = &Archive{ Name: archiveName, Version: details.Version, Suites: details.Suites, Components: details.Components, - Keyrings: archiveKeyrings, + PubKeys: archiveKeys, } } diff --git a/internal/setup/setup_test.go b/internal/setup/setup_test.go index 81decfbb..9bbf444b 100644 --- a/internal/setup/setup_test.go +++ b/internal/setup/setup_test.go @@ -1,43 +1,19 @@ package setup_test import ( - _ "embed" "os" "path/filepath" - "strings" - "github.com/ProtonMail/go-crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" . "gopkg.in/check.v1" "github.com/canonical/chisel/internal/setup" "github.com/canonical/chisel/internal/testutil" ) -func parseKeyring(ascii string) openpgp.KeyRing { - keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(ascii)) - if err != nil { - panic(err) - } - return keyring -} - -func indentLines(text string, indent string) string { - var result strings.Builder - for _, line := range strings.Split(text, "\n") { - result.WriteString(indent) - result.WriteString(line) - result.WriteByte('\n') - } - return result.String() -} - var ( - //go:embed testdata/ubuntu-archive-keyring.asc - testKeyringUbuntuArchiveASCII string - testKeyringUbuntuArchive = parseKeyring(testKeyringUbuntuArchiveASCII) - //go:embed testdata/ubuntu-master-keyring.asc - testKeyringUbuntuMasterASCII string - testKeyringUbuntuMaster = parseKeyring(testKeyringUbuntuMasterASCII) + testKey = testutil.PGPKeys["key1"] + extraTestKey = testutil.PGPKeys["key2"] ) type setupTest struct { @@ -84,6 +60,11 @@ var setupTests = []setupTest{{ version: 22.04 components: [main, other] suites: [jammy, jammy-security] + v1-public-keys: [test-key] + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` `, "slices/mydir/mypkg.yaml": ` package: mypkg @@ -98,6 +79,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy", "jammy-security"}, Components: []string{"main", "other"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -141,6 +123,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -202,6 +185,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -462,6 +446,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -676,6 +661,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -715,6 +701,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -755,6 +742,7 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -786,10 +774,16 @@ var setupTests = []setupTest{{ components: [main, universe] suites: [jammy] default: true + v1-public-keys: [test-key] bar: version: 22.04 components: [universe] suites: [jammy-updates] + v1-public-keys: [test-key] + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` `, "slices/mydir/mypkg.yaml": ` package: mypkg @@ -804,12 +798,14 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, "bar": { Name: "bar", Version: "22.04", Suites: []string{"jammy-updates"}, Components: []string{"universe"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -822,7 +818,65 @@ var setupTests = []setupTest{{ }, }, }, { - summary: "Archives with keyrings", + summary: "Extra fields in YAML are ignored (necessary for forward compatibility)", + input: map[string]string{ + "chisel.yaml": ` + format: chisel-v1 + archives: + ubuntu: + version: 22.04 + components: [main, other] + suites: [jammy, jammy-security] + v1-public-keys: [test-key] + madeUpKey1: whatever + madeUpKey2: whatever + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` + madeUpKey6: whatever + `, + "slices/mydir/mypkg.yaml": ` + package: mypkg + madeUpKey3: whatever + slices: + myslice: + madeUpKey4: whatever + contents: + /path: {madeUpKey5: whatever} + `, + }, + release: &setup.Release{ + DefaultArchive: "ubuntu", + + Archives: map[string]*setup.Archive{ + "ubuntu": { + Name: "ubuntu", + Version: "22.04", + Suites: []string{"jammy", "jammy-security"}, + Components: []string{"main", "other"}, + PubKeys: []*packet.PublicKey{testKey.PubKey}, + }, + }, + Packages: map[string]*setup.Package{ + "mypkg": { + Archive: "ubuntu", + Name: "mypkg", + Path: "slices/mydir/mypkg.yaml", + Slices: map[string]*setup.Slice{ + "myslice": { + Package: "mypkg", + Name: "myslice", + Contents: map[string]setup.PathInfo{ + "/path": {Kind: "copy"}, + }, + }, + }, + }, + }, + }, +}, { + summary: "Archives with public keys", input: map[string]string{ "chisel.yaml": ` format: chisel-v1 @@ -831,16 +885,20 @@ var setupTests = []setupTest{{ version: 22.04 components: [main, universe] suites: [jammy] - public-keys: [ubuntu-archive] + v1-public-keys: [extra-key] default: true bar: version: 22.04 components: [universe] suites: [jammy-updates] - public-keys: [ubuntu-archive, ubuntu-master] - public-keys: - ubuntu-archive: |` + "\n" + indentLines(testKeyringUbuntuArchiveASCII, "\t\t\t\t\t") + ` - ubuntu-master: |` + "\n" + indentLines(testKeyringUbuntuMasterASCII, "\t\t\t\t\t") + ` + v1-public-keys: [test-key, extra-key] + v1-public-keys: + extra-key: + id: ` + extraTestKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(extraTestKey.PubKeyArmor, "\t\t\t\t\t\t") + ` + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` `, "slices/mydir/mypkg.yaml": ` package: mypkg @@ -855,14 +913,14 @@ var setupTests = []setupTest{{ Version: "22.04", Suites: []string{"jammy"}, Components: []string{"main", "universe"}, - Keyrings: []openpgp.KeyRing{testKeyringUbuntuArchive}, + PubKeys: []*packet.PublicKey{extraTestKey.PubKey}, }, "bar": { Name: "bar", Version: "22.04", Suites: []string{"jammy-updates"}, Components: []string{"universe"}, - Keyrings: []openpgp.KeyRing{testKeyringUbuntuArchive, testKeyringUbuntuMaster}, + PubKeys: []*packet.PublicKey{testKey.PubKey, extraTestKey.PubKey}, }, }, Packages: map[string]*setup.Package{ @@ -875,7 +933,7 @@ var setupTests = []setupTest{{ }, }, }, { - summary: "Unknown keyring", + summary: "Archive without public keys", input: map[string]string{ "chisel.yaml": ` format: chisel-v1 @@ -884,23 +942,30 @@ var setupTests = []setupTest{{ version: 22.04 components: [main, universe] suites: [jammy] - public-keys: [ubuntu-archive] default: true - bar: + `, + }, + relerror: `chisel.yaml: archive "foo" missing v1-public-keys field`, +}, { + summary: "Unknown public key", + input: map[string]string{ + "chisel.yaml": ` + format: chisel-v1 + archives: + foo: version: 22.04 - components: [universe] - suites: [jammy-updates] - public-keys: [ubuntu-master] - public-keys: - ubuntu-master: |` + "\n" + indentLines(testKeyringUbuntuMasterASCII, "\t\t\t\t\t") + ` + components: [main, universe] + suites: [jammy] + v1-public-keys: [extra-key] + default: true `, "slices/mydir/mypkg.yaml": ` package: mypkg `, }, - relerror: `chisel.yaml: archive "foo" references unknown keyring "ubuntu-archive"`, + relerror: `chisel.yaml: archive "foo" refers to undefined public key "extra-key"`, }, { - summary: "Invalid keyring", + summary: "Invalid public key", input: map[string]string{ "chisel.yaml": ` format: chisel-v1 @@ -909,81 +974,59 @@ var setupTests = []setupTest{{ version: 22.04 components: [main, universe] suites: [jammy] - public-keys: [ubuntu-master] + v1-public-keys: [extra-key] default: true - public-keys: - ubuntu-master: | - G. B. Shaw's Law: - Those who can -- do. - Those who can't -- teach. - - Martin's Extension: - Those who cannot teach -- administrate. + v1-public-keys: + extra-key: + id: foo + armor: | + G. B. Shaw's Law: + Those who can -- do. + Those who can't -- teach. + + Martin's Extension: + Those who cannot teach -- administrate. `, "slices/mydir/mypkg.yaml": ` package: mypkg `, }, - relerror: `chisel.yaml: cannot parse keyring "ubuntu-master": openpgp: invalid argument: no armored data found`, + relerror: `chisel.yaml: cannot decode public key "extra-key": cannot decode armored data`, }, { - summary: "Extra fields in YAML are ignored (necessary for forward compatibility)", + summary: "Mismatched public key ID", input: map[string]string{ "chisel.yaml": ` format: chisel-v1 archives: - ubuntu: + foo: version: 22.04 - components: [main, other] - suites: [jammy, jammy-security] - madeUpKey1: whatever - madeUpKey2: whatever + components: [main, universe] + suites: [jammy] + v1-public-keys: [extra-key] + default: true + v1-public-keys: + extra-key: + id: ` + extraTestKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` `, "slices/mydir/mypkg.yaml": ` package: mypkg - madeUpKey3: whatever - slices: - myslice: - madeUpKey4: whatever - contents: - /path: {madeUpKey5: whatever} `, }, - release: &setup.Release{ - DefaultArchive: "ubuntu", - - Archives: map[string]*setup.Archive{ - "ubuntu": { - Name: "ubuntu", - Version: "22.04", - Suites: []string{"jammy", "jammy-security"}, - Components: []string{"main", "other"}, - }, - }, - Packages: map[string]*setup.Package{ - "mypkg": { - Archive: "ubuntu", - Name: "mypkg", - Path: "slices/mydir/mypkg.yaml", - Slices: map[string]*setup.Slice{ - "myslice": { - Package: "mypkg", - Name: "myslice", - Contents: map[string]setup.PathInfo{ - "/path": {Kind: "copy"}, - }, - }, - }, - }, - }, - }, + relerror: `chisel.yaml: public key "extra-key" armor has incorrect ID: expected "9568570379BF1F43", got "854BAF1AA9D76600"`, }} -const defaultChiselYaml = ` +var defaultChiselYaml = ` format: chisel-v1 archives: ubuntu: version: 22.04 components: [main, universe] + v1-public-keys: [test-key] + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` ` func (s *S) TestParseRelease(c *C) { diff --git a/internal/setup/testdata/ubuntu-archive-keyring.asc b/internal/setup/testdata/ubuntu-archive-keyring.asc deleted file mode 100644 index b185006e..00000000 --- a/internal/setup/testdata/ubuntu-archive-keyring.asc +++ /dev/null @@ -1,157 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -D/rGW6BVDiRZbn6ZT5SJQX16fbwJ5/ksKht2MgjmVIqMi5PD6dBDtUUbavYaoC+Q -6Zh3xjTuSGO2DBfodIa/xDL9SNz+LqSbN0UR5Q4I/7KHtg1ir09HYUPEjy+ZloBB -a7Zya+lgBogQS599cAhs3RE7YBB8R5kCDQRbn8HaARAA7/xscrcfy3El2LjNDMCq -I2wcnvNbNBtZxMfpc+lQFKSFGZ25KnVwRwvncKxkvwnni7gIz0S1PAKMRP4472Va -fMRRhFh2HZJalxmf4CXz+Xd3yFAbWR2RCZfAfJvaTB3/wEEHbAvmM4s0hubeTIZ6 -LcNOOC17XRBJMdreic9Dhq4fuSKMal+6WYqugr9fQaIWlIqCjHaexEukWHze6Jeh -0ixZazF7VX4f4o6TfY92YVRlXkQvJCh0LCeT5CG5r8QYlIe0iZn2VMdCEITTGgx1 -33WQBjbZ4c8zUXm9RajS0lZK0vz57AEMzIRtQQ5tlTkheuI3myl33xajOS10UE3q -ky7I1G266kerPxgjvFBe431I+iO7Wi8oJrBzvyQ+I6SkQtIG6VAX2oici77nqcd5 -FqKi97DdC4ZTCPNPnwOxk76DseLaalZc5ROk2o2Lvo31t0KThUuXsBDHS9uoc8bG -YP4Hmb02wK3D/jrCSkZob+JDaOgMnch0P92Vf391/Zk9/0jy2yWrppIKd2M3ereT -3gbvmUJP5jeVjTbmooTRFe5ZW9WYb2NBcbvQVXfwTZdK87sad6yIpwdk19kgoO8B -OcV5MF7kP9nkwxNL9B5Rp7ZLmYxqMA2ZMR2UEsWVTs3WQkVWl/1hBS6SmtgEKcOU -Sa0OKGfzn4n18icz9u6NN8EAEQEAAbRCVWJ1bnR1IEFyY2hpdmUgQXV0b21hdGlj -IFNpZ25pbmcgS2V5ICgyMDE4KSA8ZnRwbWFzdGVyQHVidW50dS5jb20+iQI4BBMB -CgAiBQJbn8HaAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCHGSDRmRvJ -PCxzEACktnJ8c/+VmqAjlgK3+YOlB23jgoHOQwZtIQrhQ2Vlr+Nu2hnotwj7i8NA -xiwl2XcnOXahPJr4zJTppgCipY9bhoN02Am0Fo1j3jJwT2W5BYJGaFye/+gge21k -YbdbB86bdS02fkmA8DsCevEEaew0WmZfWOkIlG3roatg1HE6H1WwcW4a3JDeGbXi -75vv5xvZv3IqKXOui8EXZManyd9gsqvtU0uVWiCQxuw1s4hvim7uqggzOEDZYNyx -+6deAq0cQG3OJb6IUYLFeHkKrCHHRZLlWORzz49ivE6qWOkk3vBodGqaxtUVfGSm -stykjKZ8ldXwCp+HzPW8oi80AKLwtC2fTDDLKwEv+OQLwtyBCkkoYyxZ9V9XUQoj -uv+45mRKGbQKed4ZH/EjAbIu/IVTawbpmcHyHQQNb9tvi2OMUCvKuFwqEXAPRvqb -81PWFVu3EZw2WRpdLsDsO8/T5EAReShSo1g8+HwpPiuvmLRqaLxinpBgW/COxAOl -Kbz4KgP0HSNLdSAT9DdOkUHLNX1GgEBLc+gxsuc5EYUeKRkmZ/nRRE+z3QIxCvOM -uwXWOLflNY3EiLwY9Bdgey8ES+8RqUqSCov3pAFy7Nde27xR2gr5lGDeyVadRjJl -RcYSHceghZt38RvEIzW+bXq3v2KivrjoHF58tVJcLQlM5a0mj4kCMwQQAQoAHRYh -BBU/HJ7xOV+/ADUujQv7hH8/Jy9bBQJbn8RDAAoJEAv7hH8/Jy9bbhcP/RoGnoIL -wp9eUKZQAWvOjkXiQEcZwMaWi9tt6S5IAGwWADk+z5k48MBwqhniWRi8wELBi3Ol -pEA3oHsEAjFi6ftczh5lAR22T7M9xO+gHN/NRQF4WQY/DC23MjkTrCmCmfTP8hnq -zKVceAfFjW+T/rfbbQMMAEf5TbOTkt5aVeJ5MCM78QOlp6tIFigS//a3O7C/qlni -Q50BJKtWf3TQW4CFpLQ7aniFxZXYI2Dl/sdUTfNW3i1Q7US6DlNCJELBRmjjm9KN -sfP3ZmDNnF7nITRmJnWNmeY3iyNRdHcwkfgkVBAxXa9HBfeFEoFRlsgqGh3QAU0Q -+Xv7iBMki9E/cpvd0TQbaHPYDxDRQdgEjCYJDDSDYlfNmpT42GK27PmVR7i0CIHf -qsPzes8C7VQ4KNj3OhV2aapko0UZrQUSbr/lZwwXgDrLZdEJaEZuYEQaf8ILfdxN -QIfkCUVbjEBas9Jz2Vk8H3BmoJkhLq1oil/J9hRWJIi38lFtN9+UzfPGfffoL0Pg -MkgBbvEXk/5UMwD0TzUS46QJOXtRbjM0GKASXGMD9LIwCDtQFpoLjyNSi351Z157 -E+SWDGxtgwixyPziL56UavL/eeYJWeS/WqvGzZzsAtgSujFVLKWyUaRi0NvYW3h/ -I50Tzj0Pkm8GtgvP2UqAWvy+iRpeUQ2ji0Nc -=j6+P ------END PGP PUBLIC KEY BLOCK----- diff --git a/internal/setup/testdata/ubuntu-master-keyring.asc b/internal/setup/testdata/ubuntu-master-keyring.asc deleted file mode 100644 index ec6fd997..00000000 --- a/internal/setup/testdata/ubuntu-master-keyring.asc +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBEc0weABEACyhLpI9Z2tvMhgV6AywGNKKLbeJE1Gaj1stn6t7ApNf4Q46FUi -D6WJ8voPiSqLdV3xaGLOEi+qbpIUOzGUNPNt+D0tMj72LCK3IIMUR2PnVyr+yNFa -8nB4ymArrCZERIpB75cOF3MUDMcA85AXoHd6+TjEqc3yP3tg0pUmGWrKHXP4TOSg -/cp1s67kLOk/qOjdilFAdIU02BP/XmIDxYrlzH97x/qQVJTzlBihPB4QllIp2kX6 -VD9wEA9j/n9+vXQC4B17h5NRucugtfA8Ud1016GJUfIqWdTgwU/FBx7T6XbS9mCh -6ewD7d58Yndtoo8+N/LjBMh83WrjhvO5DCAcrJL2QCJZRihaQOCPCf+iMicel8n7 -AhJ+ty/ppCLEd6PAUspPBMLVx1yKHLDc/WsG4QWiwAGrWndHDy0ZBouUfCWDNUws -FpB+/BG8mKbTRhAAtQyIdCekXfr7YndPTuL9gB9+qCSn58LCIZU68SUBv80BLpM1 -4iIP/vwZQoVVeKESqDjgpTfqitKfFjOZ+Nd3DylW3pPTDZHNXClDemPucr8ur2MA -QaYjPlsT8eMch82+eaeLDw2xFokpHAVQ3pprRNldJA03jmxvcIPfijuCW1Y7Ivg1 -wincXKPwPgIZWP/NphuqgOid8aoNBtenVrc39gMT7N2D3kiOrixnM3QxtQARAQAB -tDhVYnVudHUgQXJjaGl2ZSBNYXN0ZXIgU2lnbmluZyBLZXkgPGZ0cG1hc3RlckB1 -YnVudHUuY29tPokCNgQTAQIAIAUCRzTB4AIbAwYLCQgHAwIEFQIIAwQWAgMBAh4B -AheAAAoJEAv7hH8/Jy9bGPIQAJ/QYTiOQjUsjqA0bVIaqDOd96w/S746lKthHGpO -Tt1xyi4Lh9CVAjWiqZNE0oKVQFn98Qiswn7M6RlCVEyu4lZRZvUcntn+IV5KxRBy -7zUfHLx4wjMoLaR0N5ahAC5W7aSpQOc6wtMRw3vfxYaF7/oFabZ46b9QryC6yVVm -S5mQrRmNLA6Gg7eNR4TioZKnfDaTl5masOTrVaRlOkAQZ1YpsKeq5EsvXG17rp0C -6FlroINLgU2jvGE86OOv/YatNMajBHaK4cHzd9IPMPygSeZoN0V3n3RzqDpUUE4A -cRXc5gydPbyuuk4YrepnY65VB1KRXyUjO7gXnaWoAfqkJ2dUoO/5jRSoHED3tqeU -1NJRmTQX8xWq126xNcLw3MrjVopbqO85fIwtHF3xONDZwgjHBwqd4DN1dPBBo/rh -m/U9WOPW3vlQXmCwawsVaax11jFqyQX1+86yY6+XOGkSZDrXEJTPnG0C1feHHqCj -7wnuDEGkOFhjFpKr4YOlI/p45GhHnzYLwbK3OA0qYNN17OmRInt7WRNtDK2d+lIN -uep0Y1NZuwVC+4dwqbuuzJLyh3bP2Mg2lADWN5P6bn+e3KiEEJxppTT4k+H9RRUX -j48csEjv6COLs/dvoZlcrzHV7rzxWGSOJh70nphydKKAdbgD7Kdx4d6T+OfgACJW -0SyyiEYEEBECAAYFAkeN/lMACgkQ9t0zAhD6TNHSwQCgi7eTe4D8OXYiUbRnRCiV -Qw3k5+kAn3ho3p27J8RjLNcfpfH6fGfuG2Rh -=+CyM ------END PGP PUBLIC KEY BLOCK----- diff --git a/internal/slicer/slicer_test.go b/internal/slicer/slicer_test.go index aa9d0f51..48e21bf1 100644 --- a/internal/slicer/slicer_test.go +++ b/internal/slicer/slicer_test.go @@ -16,6 +16,10 @@ import ( "github.com/canonical/chisel/internal/testutil" ) +var ( + testKey = testutil.PGPKeys["key1"] +) + type slicerTest struct { summary string arch string @@ -497,9 +501,15 @@ var slicerTests = []slicerTest{{ version: 22.04 components: [main, universe] default: true + v1-public-keys: [test-key] bar: version: 22.04 components: [main] + v1-public-keys: [test-key] + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` `, "slices/mydir/base-files.yaml": ` package: base-files @@ -517,12 +527,17 @@ var slicerTests = []slicerTest{{ }, }} -const defaultChiselYaml = ` +var defaultChiselYaml = ` format: chisel-v1 archives: ubuntu: version: 22.04 components: [main, universe] + v1-public-keys: [test-key] + v1-public-keys: + test-key: + id: ` + testKey.ID + ` + armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t") + ` ` type testArchive struct { diff --git a/internal/testutil/pgpkeys.go b/internal/testutil/pgpkeys.go new file mode 100644 index 00000000..e999a9a3 --- /dev/null +++ b/internal/testutil/pgpkeys.go @@ -0,0 +1,182 @@ +package testutil + +import ( + "log" + + "golang.org/x/crypto/openpgp/packet" + + "github.com/canonical/chisel/internal/pgputil" +) + +type PGPKeyData struct { + ID string + PubKeyArmor string + PrivKeyArmor string + PubKey *packet.PublicKey + PrivKey *packet.PrivateKey +} + +var PGPKeys = map[string]*PGPKeyData{ + "key-ubuntu-2018": { + ID: "871920D1991BC93C", + PubKeyArmor: pubKeyUbuntu2018Armor, + }, + "key1": { + ID: "854BAF1AA9D76600", + PubKeyArmor: pubKey1Armor, + PrivKeyArmor: privKey1Armor, + }, + "key2": { + ID: "9568570379BF1F43", + PubKeyArmor: pubKey2Armor, + PrivKeyArmor: privKey2Armor, + }, +} + +func init() { + for name, key := range PGPKeys { + if key.PubKeyArmor != "" { + pubKeys, privKeys, err := pgputil.DecodeKeys([]byte(key.PubKeyArmor)) + if err != nil || len(privKeys) > 0 || len(pubKeys) != 1 || pubKeys[0].KeyIdString() != key.ID { + log.Panicf("invalid public key armored data: %s", name) + } + key.PubKey = pubKeys[0] + } + if key.PrivKeyArmor != "" { + pubKeys, privKeys, err := pgputil.DecodeKeys([]byte(key.PrivKeyArmor)) + if err != nil || len(pubKeys) > 0 || len(privKeys) != 1 || privKeys[0].KeyIdString() != key.ID { + log.Panicf("invalid private key armored data: %s", name) + } + key.PrivKey = privKeys[0] + } + } +} + +// Ubuntu Archive Automatic Signing Key (2018) . +// ID: 871920D1991BC93C. +// Useful to validate InRelease files from live archive. +const pubKeyUbuntu2018Armor = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFufwdoBEADv/Gxytx/LcSXYuM0MwKojbBye81s0G1nEx+lz6VAUpIUZnbkq +dXBHC+dwrGS/CeeLuAjPRLU8AoxE/jjvZVp8xFGEWHYdklqXGZ/gJfP5d3fIUBtZ +HZEJl8B8m9pMHf/AQQdsC+YzizSG5t5Mhnotw044LXtdEEkx2t6Jz0OGrh+5Ioxq +X7pZiq6Cv19BohaUioKMdp7ES6RYfN7ol6HSLFlrMXtVfh/ijpN9j3ZhVGVeRC8k +KHQsJ5PkIbmvxBiUh7SJmfZUx0IQhNMaDHXfdZAGNtnhzzNReb1FqNLSVkrS/Pns +AQzMhG1BDm2VOSF64jebKXffFqM5LXRQTeqTLsjUbbrqR6s/GCO8UF7jfUj6I7ta +LygmsHO/JD4jpKRC0gbpUBfaiJyLvuepx3kWoqL3sN0LhlMI80+fA7GTvoOx4tpq +VlzlE6TajYu+jfW3QpOFS5ewEMdL26hzxsZg/geZvTbArcP+OsJKRmhv4kNo6Ayd +yHQ/3ZV/f3X9mT3/SPLbJaumkgp3Yzd6t5PeBu+ZQk/mN5WNNuaihNEV7llb1Zhv +Y0Fxu9BVd/BNl0rzuxp3rIinB2TX2SCg7wE5xXkwXuQ/2eTDE0v0HlGntkuZjGow +DZkxHZQSxZVOzdZCRVaX/WEFLpKa2AQpw5RJrQ4oZ/OfifXyJzP27o03wQARAQAB +tEJVYnVudHUgQXJjaGl2ZSBBdXRvbWF0aWMgU2lnbmluZyBLZXkgKDIwMTgpIDxm +dHBtYXN0ZXJAdWJ1bnR1LmNvbT6JAjgEEwEKACIFAlufwdoCGwMGCwkIBwMCBhUI +AgkKCwQWAgMBAh4BAheAAAoJEIcZINGZG8k8LHMQAKS2cnxz/5WaoCOWArf5g6UH +beOCgc5DBm0hCuFDZWWv427aGei3CPuLw0DGLCXZdyc5dqE8mvjMlOmmAKKlj1uG +g3TYCbQWjWPeMnBPZbkFgkZoXJ7/6CB7bWRht1sHzpt1LTZ+SYDwOwJ68QRp7DRa +Zl9Y6QiUbeuhq2DUcTofVbBxbhrckN4ZteLvm+/nG9m/ciopc66LwRdkxqfJ32Cy +q+1TS5VaIJDG7DWziG+Kbu6qCDM4QNlg3LH7p14CrRxAbc4lvohRgsV4eQqsIcdF +kuVY5HPPj2K8TqpY6STe8Gh0aprG1RV8ZKay3KSMpnyV1fAKn4fM9byiLzQAovC0 +LZ9MMMsrAS/45AvC3IEKSShjLFn1X1dRCiO6/7jmZEoZtAp53hkf8SMBsi78hVNr +BumZwfIdBA1v22+LY4xQK8q4XCoRcA9G+pvzU9YVW7cRnDZZGl0uwOw7z9PkQBF5 +KFKjWDz4fCk+K6+YtGpovGKekGBb8I7EA6UpvPgqA/QdI0t1IBP0N06RQcs1fUaA +QEtz6DGy5zkRhR4pGSZn+dFET7PdAjEK84y7BdY4t+U1jcSIvBj0F2B7LwRL7xGp +SpIKi/ekAXLs117bvFHaCvmUYN7JVp1GMmVFxhIdx6CFm3fxG8QjNb5tere/YqK+ +uOgcXny1UlwtCUzlrSaP +=9AdM +-----END PGP PUBLIC KEY BLOCK----- +` + +// Test-purpose RSA 2048 bits signing key-pairs without a passphrase. +// ID: 854BAF1AA9D76600. User: "foo-bar ". +const pubKey1Armor = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGVs8P4BCADPh/fNnw2AI1JCYf+3p4jkcFQPLVsUkoTZk8OXjCxy+UP9Jd2m +xnxat7a0JEJZa0aWCmtlSL1XR+kFKBrd7Ry5jOHYjuDKx4kTmDUbezPnjoZIGDNX +j5cdNuMLpOINZweNNWDKRdRvhj5QX89/DYwPrLkNFwwjXjlj5tjU6RUkROYJBGPe +G2ns2cZtVbYMh3FDU9YRfp/hUqGVf+UFRyUw+mo1TUlk5F7fnfwEQmsppDHvfTNJ +yjEMZD7nReTEeMy12GV2wysOwWMPEb2PSE/+Od7AKn5dFA7w3kyLCzAxYp6o7IE/ ++RY8YzAJe6GmLwhTWtylMV1xteQhZkEe/QGXABEBAAG0EWZvby1iYXIgPGZvb0Bi +YXI+iQFOBBMBCgA4FiEEDp0LAdsRnT9gfhU5hUuvGqnXZgAFAmVs8P4CGwMFCwkI +BwIGFQoJCAsCBBYCAwECHgECF4AACgkQhUuvGqnXZgCHZAf/b/rkMz2UY42LhuvJ +xDW7KbdBI+UgFp2k2tg2SkLM27GdcztpcNn/RE9U1vc8uCI05MbMhKQ+oq4RmO6i +QbCPPGy1Mgf61Fku0JTZGEKg+4DKNmnVkSpiOc03z3G2Gyi2m9G2u+HdJhXHumej +7NXkQvVFxXzDnzntbnmkM0fMfO+wdP5/EFjJbHC47yAAds/yspfk5qIHu6PHrTVB ++wJGwOJdwJ1+2zis5ONE8NexfSrDzjGJoKAFtlMwNNDZ39JlkguMB0M5SxoGRXxQ +ZE4DhPntUIW0qsE6ChmmjssjSDeg75rwgc+hjNDunKQhKNpjVVFGF4uceV5EQ084 +F4nA5w== +=ZXap +-----END PGP PUBLIC KEY BLOCK----- +` +const privKey1Armor = ` +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBGVs8P4BCADPh/fNnw2AI1JCYf+3p4jkcFQPLVsUkoTZk8OXjCxy+UP9Jd2m +xnxat7a0JEJZa0aWCmtlSL1XR+kFKBrd7Ry5jOHYjuDKx4kTmDUbezPnjoZIGDNX +j5cdNuMLpOINZweNNWDKRdRvhj5QX89/DYwPrLkNFwwjXjlj5tjU6RUkROYJBGPe +G2ns2cZtVbYMh3FDU9YRfp/hUqGVf+UFRyUw+mo1TUlk5F7fnfwEQmsppDHvfTNJ +yjEMZD7nReTEeMy12GV2wysOwWMPEb2PSE/+Od7AKn5dFA7w3kyLCzAxYp6o7IE/ ++RY8YzAJe6GmLwhTWtylMV1xteQhZkEe/QGXABEBAAEAB/4jvxdbdyiTqEHchlXO +NBDbzE9mV9km53/znESl/3KOkUn5OkL+HZVA6QES8WXuUhCT+pJ6HTfj51KHXVuX +W2bFvTMPorispQcC9YY8SBHuMjoGBAkf7W9JjHE6SbnYNiVyWL3lyXZoiVaFcKNk +jphQAN/VFeG029+FyjcSIV3PY7FWI4Q1dyqyf78iWa6I400cmyGFvZDSps/oo3sT +0xcjdLL5AaXyR0FtZoSrltioYzp4cnYDI2ES9PT7uR6MQ7AwUamUQ/7dUR6zSi1o +NbHVOYItsZEsY8N/1vUxW+Ps0bbgZd9ob6n+1beQIeSMhJiW0g2NiqlZXo8GELNp +LNOBBADl+tu0iX0DCTJ5fnDeiWgMv+sPA9pcACKhnxDuOXMJjV/gGY2XtKzP0o68 +y8N5Nry0UG3wHMlgqp5qY8ZkXfH3zMmIezG5C6HZQ7A44wem3iBYj8Z1bjpT8AW7 +rFi+1iBDmZ4whHzsxLp8XB/cugAh/g3bo6rJl2bCaQPnpsSygQQA5wLnFL8pnj4M +kNzefp/ZFGTstB7AC1Dfkja9QTfimZpJZj/5XXyewAgmqQt9uersmLHfXhS3sgrk +kko74ZEZY5PCInsbcvUkgRxgw/JnjWdHLVUOMMd12RVQU9BOVf2kN8sEWCQbqzsM +H9IEtFjXXyyubmb4euI25xs1ptxk+BcD/j1J5bu6RZfP2IfEeBPu4w8zK5WOioLY +dia8kvzScIRvREB6DbYCifirx0gSuZSCyo+zm/KfZCof89ihOZ4e3OAWQDqajfQH +AGoXJCN9LRJsGe/x79LHuOx71x1MbTTvOUlYJTD9+cHzWRzKHb2ecFL6jaJb4OhY +RP4t194OXMHdQ2q0EWZvby1iYXIgPGZvb0BiYXI+iQFOBBMBCgA4FiEEDp0LAdsR +nT9gfhU5hUuvGqnXZgAFAmVs8P4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AA +CgkQhUuvGqnXZgCHZAf/b/rkMz2UY42LhuvJxDW7KbdBI+UgFp2k2tg2SkLM27Gd +cztpcNn/RE9U1vc8uCI05MbMhKQ+oq4RmO6iQbCPPGy1Mgf61Fku0JTZGEKg+4DK +NmnVkSpiOc03z3G2Gyi2m9G2u+HdJhXHumej7NXkQvVFxXzDnzntbnmkM0fMfO+w +dP5/EFjJbHC47yAAds/yspfk5qIHu6PHrTVB+wJGwOJdwJ1+2zis5ONE8NexfSrD +zjGJoKAFtlMwNNDZ39JlkguMB0M5SxoGRXxQZE4DhPntUIW0qsE6ChmmjssjSDeg +75rwgc+hjNDunKQhKNpjVVFGF4uceV5EQ084F4nA5w== +=VBWI +-----END PGP PRIVATE KEY BLOCK----- +` + +// Test-purpose RSA 1024 bits signing key-pairs without a passphrase. +// ID: 9568570379BF1F43. User: "Extra Test Key ". +const pubKey2Armor = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EZXAwcgEEAMBQ4Qx6xam1k1hyjPrKQfCnGRBBm2+Lw9DHQcz0lreH51iZEVkS +fACbPHI9A7NX8xdX1cMLpaTQCT3h30WwuLuNAo1IdYcdGpfzFzd6rqS5OCItj+3u +XZrTlS8QxVVShSPYFfxYaIXKCZF9G+RTKD0rWQwkMwNHZ4vJGBm7qKytABEBAAG0 +GUV4dHJhIFRlc3QgS2V5IDx0ZXN0QGtleT6IzgQTAQoAOBYhBFQrMoAoDwtUfB+G +BpVoVwN5vx9DBQJlcDByAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJVo +VwN5vx9Dy80D/iUzJkfT8lsH0vZ2jcpgcyjtZqrIfOMLYk8DqoYD/1wDGx4TIzg/ +bpqDHxBCDmBaxY6+ps9IaBcsD1whjyX4AZK6FykV8d9GAc+3b9t2EPe92LV3XKaT +rwF9bjDSJZUUz1I31YTnHpBiRU+hWuf7OVjnLcEAB8mMa7Y6YN37qT44 +=U79/ +-----END PGP PUBLIC KEY BLOCK----- +` +const privKey2Armor = ` +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQHYBGVwMHIBBADAUOEMesWptZNYcoz6ykHwpxkQQZtvi8PQx0HM9Ja3h+dYmRFZ +EnwAmzxyPQOzV/MXV9XDC6Wk0Ak94d9FsLi7jQKNSHWHHRqX8xc3eq6kuTgiLY/t +7l2a05UvEMVVUoUj2BX8WGiFygmRfRvkUyg9K1kMJDMDR2eLyRgZu6isrQARAQAB +AAP+LXyDuiSor0rt0o/ndeLURVP0auKlnbS4SB902gHoyvh3OL6deoyTbT5KRffV +8fuFmNoSymrtDwYQhYUwvqY9jt+lVSKDseqLkF5C92VZFWpjiYDOqZzoBfVUDZo5 +NffyIxuG5X33o9yBmUk29PWcLqzSanxg/TmXy63pp4sBYfECAN3GgiWxwrQTtv0X +OUuSKbvnDVyM86R7Hdo08hmwB/6qhGibw5KBko+h+kBsIo1naEzzGsXWUjLk8BbZ +qPTRGrECAN3+ijctJPm+JprWjJlJ5KrdXlIMG5x87vtdp5ZzctsmY97GMBaW+SvW +uuBHfiY7xFUru8304gWd/YAwTdxVeL0CALjGKCTWPhZaRJ+ew9iryVgFEznaNAgO +pzVXr3yllNdinGWjvbyEkn1y7OlzH0REg9jOsc82Bbz4aiDm19Qr/1KtR7QZRXh0 +cmEgVGVzdCBLZXkgPHRlc3RAa2V5PojOBBMBCgA4FiEEVCsygCgPC1R8H4YGlWhX +A3m/H0MFAmVwMHICGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQlWhXA3m/ +H0PLzQP+JTMmR9PyWwfS9naNymBzKO1mqsh84wtiTwOqhgP/XAMbHhMjOD9umoMf +EEIOYFrFjr6mz0hoFywPXCGPJfgBkroXKRXx30YBz7dv23YQ973YtXdcppOvAX1u +MNIllRTPUjfVhOcekGJFT6Fa5/s5WOctwQAHyYxrtjpg3fupPjg= +=JbF+ +-----END PGP PRIVATE KEY BLOCK----- +` diff --git a/internal/testutil/reindent.go b/internal/testutil/reindent.go index 10c68a0d..d4ff16ff 100644 --- a/internal/testutil/reindent.go +++ b/internal/testutil/reindent.go @@ -38,3 +38,26 @@ func Reindent(in string) []byte { } return buf.Bytes() } + +// PrefixEachLine indents each line in the provided string with the prefix. +func PrefixEachLine(text string, prefix string) string { + var result strings.Builder + lines := strings.Split(text, "\n") + lastNewline := false + if strings.HasSuffix(text, "\n") { + // Skip iterating over the empty line. + lines = lines[:len(lines)-1] + lastNewline = true + } + for i, line := range lines { + result.WriteString(prefix) + result.WriteString(line) + if i == len(lines)-1 && !lastNewline { + // Do not add the last newline if the text did not have it to begin with. + continue + } else { + result.WriteString("\n") + } + } + return result.String() +} diff --git a/internal/testutil/reindent_test.go b/internal/testutil/reindent_test.go index d3b74a59..4b14cc9c 100644 --- a/internal/testutil/reindent_test.go +++ b/internal/testutil/reindent_test.go @@ -3,8 +3,9 @@ package testutil_test import ( "strings" - "github.com/canonical/chisel/internal/testutil" . "gopkg.in/check.v1" + + "github.com/canonical/chisel/internal/testutil" ) type reindentTest struct { @@ -70,3 +71,38 @@ func (*S) testReindent(c *C, test reindentTest) { } c.Assert(string(reindented), Equals, test.result) } + +type prefixEachLineTest struct { + raw, prefix, result string +} + +var prefixEachLineTests = []prefixEachLineTest{{ + raw: "a\n\tb\n \t\tc\td\n\t ", + prefix: "foo", + result: "fooa\nfoo\tb\nfoo \t\tc\td\nfoo\t ", +}, { + raw: "foo", + prefix: "pref", + result: "preffoo", +}, { + raw: "", + prefix: "p", + result: "p", +}, { + raw: "\n", + prefix: "\t", + result: "\t\n", +}, { + raw: "\n\n", + prefix: "\t", + result: "\t\n\t\n", +}} + +func (s *S) TestPrefixEachLine(c *C) { + for _, test := range prefixEachLineTests { + c.Logf("Test: %#v", test) + + prefixed := testutil.PrefixEachLine(test.raw, test.prefix) + c.Assert(prefixed, Equals, test.result) + } +}