Add recipe that automatically uploads the latest version of cosign to CIPD

* Uses the github api to get the latest release from sigstore/cosign, then uses it to find the specific artifacts needed based on the os
* Downloads the binary/certificates
* Uses the current version of cosign to verify the new version is valid
* If it is verified to be ok, it is uploaded to CIPD if the version is a new version

Exmaple build: https://ci.chromium.org/raw/build/logs.chromium.org/flutter/led/drewroen_google.com/6adad536aa2eede8631c572002a2cdb852b11ce571820acc133d3cb1793b9a48/+/build.proto?server=chromium-swarm.appspot.com

Buganizer: https://b.corp.google.com/issues/238423446

Change-Id: I3ac83a04cf13911d2c8628fadc5e12972b40f336
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/31780
Reviewed-by: Godofredo Contreras <godofredoc@google.com>
Reviewed-by: Jesse Seales <jseales@google.com>
Commit-Queue: Drew Roen <drewroen@google.com>
diff --git a/recipes/cipd/cosign.expected/cosign.json b/recipes/cipd/cosign.expected/cosign.json
new file mode 100644
index 0000000..aab3864
--- /dev/null
+++ b/recipes/cipd/cosign.expected/cosign.json
@@ -0,0 +1,323 @@
+[
+  {
+    "cmd": [
+      "curl",
+      "https://api.github.com/repos/sigstore/cosign/releases"
+    ],
+    "name": "Get cosign releases from github"
+  },
+  {
+    "cmd": [
+      "curl",
+      "https://api.github.com/releases/1"
+    ],
+    "name": "Get artifacts from sigstore/cosign for a specific release version"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-darwin-amd64",
+      "-o",
+      "[START_DIR]/cosign/darwin/bin/cosign",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download darwin cosign binary"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-darwin-amd64-keyless.pem",
+      "-o",
+      "[START_DIR]/cosign/darwin/certificate/cosign-cert.pem",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download darwin cosign certificate"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-darwin-amd64-keyless.sig",
+      "-o",
+      "[START_DIR]/cosign/darwin/certificate/cosign-sig.sig",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download darwin cosign signature"
+  },
+  {
+    "cmd": [
+      "chmod",
+      "755",
+      "[START_DIR]/cosign/darwin/bin/cosign"
+    ],
+    "name": "Make darwin cosign binary executable"
+  },
+  {
+    "cmd": [
+      "cosign",
+      "verify-blob",
+      "--cert",
+      "[START_DIR]/cosign/darwin/certificate/cosign-cert.pem",
+      "--signature",
+      "[START_DIR]/cosign/darwin/certificate/cosign-sig.sig",
+      "[START_DIR]/cosign/darwin/bin/cosign"
+    ],
+    "name": "Verify darwin cosign binary is legitimate"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-in",
+      "[START_DIR]/cosign/darwin",
+      "-name",
+      "flutter/tools/cosign/mac-amd64",
+      "-out",
+      "cosign.zip",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "build mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/mac-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-register",
+      "cosign.zip",
+      "-ref",
+      "latest",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "register flutter/tools/cosign/mac-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/mac-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@flutter/tools/cosign/mac-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/mac-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-linux-amd64",
+      "-o",
+      "[START_DIR]/cosign/linux/bin/cosign",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download linux cosign binary"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-linux-amd64-keyless.pem",
+      "-o",
+      "[START_DIR]/cosign/linux/certificate/cosign-cert.pem",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download linux cosign certificate"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-linux-amd64-keyless.sig",
+      "-o",
+      "[START_DIR]/cosign/linux/certificate/cosign-sig.sig",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download linux cosign signature"
+  },
+  {
+    "cmd": [
+      "chmod",
+      "755",
+      "[START_DIR]/cosign/linux/bin/cosign"
+    ],
+    "name": "Make linux cosign binary executable"
+  },
+  {
+    "cmd": [
+      "cosign",
+      "verify-blob",
+      "--cert",
+      "[START_DIR]/cosign/linux/certificate/cosign-cert.pem",
+      "--signature",
+      "[START_DIR]/cosign/linux/certificate/cosign-sig.sig",
+      "[START_DIR]/cosign/linux/bin/cosign"
+    ],
+    "name": "Verify linux cosign binary is legitimate"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-in",
+      "[START_DIR]/cosign/linux",
+      "-name",
+      "flutter/tools/cosign/linux-amd64",
+      "-out",
+      "cosign.zip",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "build linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-register",
+      "cosign.zip",
+      "-ref",
+      "latest",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "register flutter/tools/cosign/linux-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/linux-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@flutter/tools/cosign/linux-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/linux-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-windows-amd64.exe",
+      "-o",
+      "[START_DIR]/cosign/windows/bin/cosign.exe",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download windows cosign binary"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-windows-amd64.exe-keyless.pem",
+      "-o",
+      "[START_DIR]/cosign/windows/certificate/cosign-cert.exe.pem",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download windows cosign certificate"
+  },
+  {
+    "cmd": [
+      "curl",
+      "-L",
+      "cosign-windows-amd64.exe-keyless.sig",
+      "-o",
+      "[START_DIR]/cosign/windows/certificate/cosign-sig.exe.sig",
+      "--create-dirs"
+    ],
+    "infra_step": true,
+    "name": "Download windows cosign signature"
+  },
+  {
+    "cmd": [
+      "cosign",
+      "verify-blob",
+      "--cert",
+      "[START_DIR]/cosign/windows/certificate/cosign-cert.exe.pem",
+      "--signature",
+      "[START_DIR]/cosign/windows/certificate/cosign-sig.exe.sig",
+      "[START_DIR]/cosign/windows/bin/cosign.exe"
+    ],
+    "name": "Verify windows cosign binary is legitimate"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-build",
+      "-in",
+      "[START_DIR]/cosign/windows",
+      "-name",
+      "flutter/tools/cosign/windows-amd64",
+      "-out",
+      "cosign.zip",
+      "-hash-algo",
+      "sha256",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "build windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/windows-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "pkg-register",
+      "cosign.zip",
+      "-ref",
+      "latest",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "name": "register flutter/tools/cosign/windows-amd64",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"package\": \"flutter/tools/cosign/windows-amd64\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LINK@flutter/tools/cosign/windows-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/windows-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/cipd/cosign.py b/recipes/cipd/cosign.py
new file mode 100644
index 0000000..8ef69bd
--- /dev/null
+++ b/recipes/cipd/cosign.py
@@ -0,0 +1,275 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+from datetime import datetime
+
+PYTHON_VERSION_COMPATIBILITY = 'PY3'
+
+DEPS = [
+    'flutter/repo_util',
+    'flutter/flutter_deps',
+    'recipe_engine/cipd',
+    'recipe_engine/context',
+    'recipe_engine/path',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/raw_io',
+    'recipe_engine/step',
+]
+
+# This recipe builds the cosign CIPD package.
+def RunSteps(api):
+  env = {}
+  env_prefixes = {}
+  api.flutter_deps.required_deps(
+      env, env_prefixes, api.properties.get('dependencies', [])
+  )
+
+  cosign_default_dir = api.path['start_dir'].join('cosign')
+
+  cosign_download_uris = GetLatestCosignDownloadUris(api)
+
+  for platform in ['darwin', 'linux', 'windows']:
+    cosign_dir = cosign_default_dir.join(platform)
+
+    DownloadCosignArtifacts(api, cosign_dir, platform, cosign_download_uris)
+
+    with api.context(env=env, env_prefixes=env_prefixes):
+      VerifyCosignArtifactSignature(api, cosign_dir, platform)
+
+    UploadCosignToCipd(api, cosign_dir, platform)
+
+
+def GetLatestCosignDownloadUris(api):
+  """Gets the list of latest sigstore/cosign binary urls.
+
+  Queries Github for a list of cosign releases, then picks the latest release,
+  queries the artifacts for that release, and returns a list of cosign binary
+  urls for that release.
+
+  Args:
+    api: luci api object.
+  """
+  cosign_releases_raw_response = api.step('Get cosign releases from github',
+        cmd=['curl', 'https://api.github.com/repos/sigstore/cosign/releases'],
+        stdout=api.raw_io.output_text()
+        ).stdout
+  cosign_releases = json.loads(cosign_releases_raw_response)
+
+  latest_release = max(
+    cosign_releases,
+    key=lambda release: datetime.strptime(
+      release.get('published_at'), '%Y-%m-%dT%H:%M:%SZ')
+  ).get('url')
+
+  release_artifacts_raw_response = api.step(
+        'Get artifacts from sigstore/cosign for a specific release version',
+        cmd=['curl', latest_release],
+        stdout=api.raw_io.output_text()
+        ).stdout
+  release_artifacts = json.loads(release_artifacts_raw_response)
+
+  release_artifacts_download_uris = list(
+    map(
+      lambda asset:
+      asset.get('browser_download_url'),
+      release_artifacts.get('assets')
+    )
+  )
+
+  return release_artifacts_download_uris
+
+def DownloadCosignArtifacts(api, cosign_dir, platform, cosign_download_uris):
+  """Downloads the latest cosign binary, certificate, and signature.
+
+  Takes a list of cosign download uris and finds the binary, certificate, and
+  signature urls based on the platform. Then, the three files are downloaded to
+  the cosign directory.
+
+  Args:
+    api: luci api object.
+    cosign_dir(str): the folder where the cosign binary/certificate/signature
+      will be downloaded.
+    platform(str): the platform of the binary that needs to be downloaded
+      (windows, linux, darwin)
+    cosign_download_uris(list(str)): a list of all the download uris for a
+      specific cosign release.
+  """
+  exe = '.exe' if platform == 'windows' else ''
+  cosign_base_name = 'cosign-%s-amd64%s' % (platform, exe)
+
+  cosign_binary_download_uri = next(
+    filter(
+      lambda uri:
+      uri.endswith(cosign_base_name),
+      cosign_download_uris
+    )
+  )
+
+  cosign_certificate_download_uri = next(
+    filter(
+      lambda uri:
+      uri.endswith('%s-keyless.pem' % cosign_base_name),
+      cosign_download_uris
+    )
+  )
+
+  cosign_signature_download_uri = next(
+    filter(
+      lambda uri:
+      uri.endswith('%s-keyless.sig' % cosign_base_name),
+      cosign_download_uris
+    )
+  )
+
+  api.step(
+    'Download %s cosign binary' % platform,
+    [
+      'curl', '-L', cosign_binary_download_uri,
+      '-o', cosign_dir.join('bin', 'cosign%s' % exe),
+      '--create-dirs'
+    ],
+    infra_step=True
+  )
+
+  api.step(
+    'Download %s cosign certificate' % platform,
+    [
+      'curl', '-L', cosign_certificate_download_uri,
+      '-o', cosign_dir.join("certificate", "cosign-cert%s.pem" % exe),
+      '--create-dirs'
+    ],
+    infra_step=True
+  )
+
+  api.step(
+    'Download %s cosign signature' % platform,
+    [
+      'curl', '-L', cosign_signature_download_uri,
+      '-o', cosign_dir.join("certificate", "cosign-sig%s.sig" % exe),
+      '--create-dirs'
+    ],
+    infra_step=True
+  )
+
+  if platform == 'linux' or platform == 'darwin':
+    api.step(
+      'Make %s cosign binary executable' % platform,
+      [
+        'chmod',
+        '755',
+        cosign_dir.join('bin', 'cosign%s' % exe)
+      ]
+    )
+
+
+def VerifyCosignArtifactSignature(api, cosign_dir, platform):
+  """Verifies the cosign artifact is legitimate
+
+  Uses the cosign release signature and certificate to ensure that the cosign
+  binary is legitimate. This is done using the current version of cosign in
+  CIPD.
+
+  Args:
+    api: luci api object.
+    cosign_dir(str): the folder where the cosign binary/certificate/signature
+      is located.
+    platform(str): the platform of the binary (windows, linux, darwin)
+  """
+  exe = '.exe' if platform == 'windows' else ''
+
+  api.step(
+    'Verify %s cosign binary is legitimate' % platform,
+    [
+      'cosign',
+      'verify-blob',
+      '--cert',
+      cosign_dir.join("certificate", "cosign-cert%s.pem" % exe),
+      '--signature',
+      cosign_dir.join("certificate", "cosign-sig%s.sig" % exe),
+      cosign_dir.join("bin", "cosign%s" % exe)
+    ]
+  )
+
+
+def UploadCosignToCipd(api, cosign_dir, platform):
+  """Uploads cosign to CIPD.
+
+  Upload the cosign binary, certificate, and signature to CIPD and adds the
+  latest tag to this version.
+
+  Args:
+    api: luci api object.
+    cosign_dir(str): the folder where the cosign binary/certificate/signature
+      is located.
+    platform(str): the platform of the binary (windows, linux, darwin)
+  """
+  cipd_platform = 'mac' if platform == 'darwin' else platform
+  cipd_package_name = 'flutter/tools/cosign/%s-amd64' % cipd_platform
+  cipd_zip_path = 'cosign.zip'
+  api.cipd.build(cosign_dir, cipd_zip_path, cipd_package_name)
+  api.cipd.register(cipd_package_name, cipd_zip_path, refs=['latest'])
+
+
+def GenTests(api):
+  yield api.test(
+    'cosign',
+    api.properties(cosign_version='v1.0'),
+    api.platform('linux', 64),
+    api.step_data(
+        'Get cosign releases from github',
+        stdout=api.raw_io.output_text('''
+        [
+          {
+            "url": "https://api.github.com/releases/1",
+            "published_at": "2022-06-03T14:08:35Z"
+          },
+          {
+            "url": "https://api.github.com/releases/2",
+            "published_at": "2022-06-02T14:08:35Z"
+          }
+        ]
+        ''')
+    ) +
+    api.step_data(
+        'Get artifacts from sigstore/cosign for a specific release version',
+        stdout=api.raw_io.output_text('''
+        {
+          "assets":[
+            {
+              "browser_download_url":"cosign-linux-amd64"
+            },
+            {
+              "browser_download_url":"cosign-linux-amd64-keyless.pem"
+            },
+            {
+              "browser_download_url":"cosign-linux-amd64-keyless.sig"
+            },
+            {
+              "browser_download_url":"cosign-darwin-amd64"
+            },
+            {
+              "browser_download_url":"cosign-darwin-amd64-keyless.pem"
+            },
+            {
+              "browser_download_url":"cosign-darwin-amd64-keyless.sig"
+            },
+            {
+              "browser_download_url":"cosign-windows-amd64.exe"
+            },
+            {
+              "browser_download_url":"cosign-windows-amd64.exe-keyless.pem"
+            },
+            {
+              "browser_download_url":"cosign-windows-amd64.exe-keyless.sig"
+            },
+            {
+              "browser_download_url":"some-other-artifact"
+            }
+          ]
+        }
+        ''')
+      )
+    )