Read Apple notarization issuer ID from Key Vault (#38646)

## What changed

- Load the Apple issuer ID from the notarization key's `apple-issuer-id`
  tag alongside its key ID and pinned version.
- Require the tag to contain a valid UUID before creating the notarization JWT.
- Remove the separate `APPLE_NOTARIZATION_ISSUER_ID` environment variable and
  release workflow secret wiring.

## Testing

- Cover valid, missing, empty, and malformed issuer ID tags in the macOS
  notarization tests.

GitOrigin-RevId: c42da96a36293cf39312d8238f958f6898247f19
This commit is contained in:
Eric Burke
2026-08-14 20:21:43 +00:00
committed by copyberry
parent fe556c4b6c
commit cce33123a1
5 changed files with 35 additions and 12 deletions

View File

@@ -70,7 +70,6 @@ done
missing_environment=0
for variable_name in \
APPLE_NOTARIZATION_ISSUER_ID \
APPLE_NOTARIZATION_AKV_KEY_NAME \
AZURE_KEYVAULT_NAME
do

View File

@@ -68,7 +68,6 @@ fi
missing_environment=0
for variable_name in \
APPLE_NOTARIZATION_ISSUER_ID \
APPLE_NOTARIZATION_AKV_KEY_NAME \
AZURE_KEYVAULT_NAME
do

View File

@@ -46,7 +46,6 @@ class NotarizationConfiguration:
"""Load and validate the notarization configuration."""
required = {
"issuer_id": "APPLE_NOTARIZATION_ISSUER_ID",
"vault_name": "AZURE_KEYVAULT_NAME",
"vault_key_name": "APPLE_NOTARIZATION_AKV_KEY_NAME",
}
@@ -79,7 +78,8 @@ class NotarizationConfiguration:
command.extend(
[
"--query",
'{id:key.kid,apple_key_id:tags."apple-key-id"}',
'{id:key.kid,apple_key_id:tags."apple-key-id",'
'apple_issuer_id:tags."apple-issuer-id"}',
"--output",
"json",
"--only-show-errors",
@@ -95,6 +95,7 @@ class NotarizationConfiguration:
metadata = json.loads(result.stdout)
returned_key_id = metadata["id"]
apple_key_id = metadata["apple_key_id"]
apple_issuer_id = metadata["apple_issuer_id"]
except (json.JSONDecodeError, KeyError, TypeError) as error:
raise NotarizationError("Notarization key metadata is invalid") from error
@@ -112,7 +113,16 @@ class NotarizationConfiguration:
raise NotarizationError("Unexpected notarization key version")
if not isinstance(apple_key_id, str) or not apple_key_id.strip():
raise NotarizationError("Notarization key must have an apple-key-id tag")
if not isinstance(apple_issuer_id, str) or not apple_issuer_id.strip():
raise NotarizationError("Notarization key must have an apple-issuer-id tag")
if not re.fullmatch(
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
apple_issuer_id.strip(),
):
raise NotarizationError("Notarization apple-issuer-id tag must contain a valid UUID")
values["issuer_id"] = apple_issuer_id.strip()
values["apple_key_id"] = apple_key_id.strip()
values["vault_key_version"] = returned_key_version
return cls(**values)

View File

@@ -15,14 +15,13 @@ sys.path.insert(0, str(SIGNING_DIRECTORY))
import notarize_with_akv as notary # noqa: E402
CONFIGURATION = notary.NotarizationConfiguration(
"issuer-id",
"01234567-89ab-cdef-0123-456789abcdef",
"APPLEKEY01",
"notary-vault",
"example-signing-key",
"0123456789abcdef" * 2,
)
ENVIRONMENT = {
"APPLE_NOTARIZATION_ISSUER_ID": CONFIGURATION.issuer_id,
"AZURE_KEYVAULT_NAME": CONFIGURATION.vault_name,
"APPLE_NOTARIZATION_AKV_KEY_NAME": CONFIGURATION.vault_key_name,
}
@@ -44,8 +43,12 @@ def azure_response(payload):
class NotarizationTest(unittest.TestCase):
def test_validates_the_apple_key_tag_and_pins_its_version(self) -> None:
metadata = {"id": CONFIGURATION.versioned_key_id, "apple_key_id": "APPLEKEY01"}
def test_validates_the_apple_key_tags_and_pins_its_version(self) -> None:
metadata = {
"id": CONFIGURATION.versioned_key_id,
"apple_key_id": "APPLEKEY01",
"apple_issuer_id": CONFIGURATION.issuer_id,
}
with (
patch.dict(os.environ, ENVIRONMENT, clear=True),
patch.object(
@@ -61,7 +64,19 @@ class NotarizationTest(unittest.TestCase):
show.return_value = azure_response({**metadata, "apple_key_id": None})
with self.assertRaisesRegex(notary.NotarizationError, "apple-key-id tag"):
notary.NotarizationConfiguration.from_environment()
self.assertIn('{id:key.kid,apple_key_id:tags."apple-key-id"}', show.call_args.args[0])
for issuer_id in (None, "", " "):
with self.subTest(issuer_id=issuer_id):
show.return_value = azure_response({**metadata, "apple_issuer_id": issuer_id})
with self.assertRaisesRegex(notary.NotarizationError, "apple-issuer-id tag"):
notary.NotarizationConfiguration.from_environment()
show.return_value = azure_response({**metadata, "apple_issuer_id": "invalid-issuer"})
with self.assertRaisesRegex(notary.NotarizationError, "valid UUID"):
notary.NotarizationConfiguration.from_environment()
self.assertIn(
'{id:key.kid,apple_key_id:tags."apple-key-id",'
'apple_issuer_id:tags."apple-issuer-id"}',
show.call_args.args[0],
)
invalid_environment = {**ENVIRONMENT, "APPLE_NOTARIZATION_AKV_KEY_NAME": "../bad"}
with (
@@ -80,6 +95,9 @@ class NotarizationTest(unittest.TestCase):
token = notary.create_apple_jwt(CONFIGURATION, issued_at=1_780_000_000)
header, claims, encoded_signature = token.split(".")
self.assertEqual(json.loads(notary.base64url_decode(header))["kid"], "APPLEKEY01")
self.assertEqual(
json.loads(notary.base64url_decode(claims))["iss"], CONFIGURATION.issuer_id
)
self.assertEqual(
json.loads(notary.base64url_decode(claims))["scope"],
["/notary/v2"],

View File

@@ -538,7 +538,6 @@ jobs:
env:
TARGET: ${{ matrix.target }}
BINARIES: ${{ matrix.binaries }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
run: |
@@ -589,7 +588,6 @@ jobs:
shell: bash
env:
TARGET: ${{ matrix.target }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
run: |
@@ -923,7 +921,6 @@ jobs:
shell: bash
env:
TARGET: ${{ matrix.target }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
run: |