diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index dec6250a6..60e9bb278 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -5683,6 +5683,29 @@
- [X] verify_email_identity
+## signer
+
+23% implemented
+
+- [ ] add_profile_permission
+- [X] cancel_signing_profile
+- [ ] describe_signing_job
+- [ ] get_signing_platform
+- [X] get_signing_profile
+- [ ] list_profile_permissions
+- [ ] list_signing_jobs
+- [X] list_signing_platforms
+- [ ] list_signing_profiles
+- [ ] list_tags_for_resource
+- [X] put_signing_profile
+- [ ] remove_profile_permission
+- [ ] revoke_signature
+- [ ] revoke_signing_profile
+- [ ] start_signing_job
+- [ ] tag_resource
+- [ ] untag_resource
+
+
## sns
55% implemented
@@ -6370,7 +6393,6 @@
- servicecatalog-appregistry
- sesv2
- shield
-- signer
- sms
- sms-voice
- snow-device-management
diff --git a/docs/docs/services/signer.rst b/docs/docs/services/signer.rst
new file mode 100644
index 000000000..fffac2fae
--- /dev/null
+++ b/docs/docs/services/signer.rst
@@ -0,0 +1,55 @@
+.. _implementedservice_signer:
+
+.. |start-h3| raw:: html
+
+
+
+.. |end-h3| raw:: html
+
+
+
+======
+signer
+======
+
+.. autoclass:: moto.signer.models.SignerBackend
+
+|start-h3| Example usage |end-h3|
+
+.. sourcecode:: python
+
+ @mock_signer
+ def test_signer_behaviour:
+ boto3.client("signer")
+ ...
+
+
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [ ] add_profile_permission
+- [X] cancel_signing_profile
+- [ ] describe_signing_job
+- [ ] get_signing_platform
+- [X] get_signing_profile
+- [ ] list_profile_permissions
+- [ ] list_signing_jobs
+- [X] list_signing_platforms
+
+ Pagination is not yet implemented. The parameters category, partner, target are not yet implemented
+
+
+- [ ] list_signing_profiles
+- [ ] list_tags_for_resource
+- [X] put_signing_profile
+
+ The following parameters are not yet implemented: SigningMaterial, Overrides, SigningParamaters
+
+
+- [ ] remove_profile_permission
+- [ ] revoke_signature
+- [ ] revoke_signing_profile
+- [ ] start_signing_job
+- [ ] tag_resource
+- [ ] untag_resource
+
diff --git a/moto/__init__.py b/moto/__init__.py
index c99b6f3b6..6b62b5efb 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -137,6 +137,7 @@ mock_sdb = lazy_load(".sdb", "mock_sdb")
mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager")
mock_ses = lazy_load(".ses", "mock_ses")
mock_servicediscovery = lazy_load(".servicediscovery", "mock_servicediscovery")
+mock_signer = lazy_load(".signer", "mock_signer", boto3_name="signer")
mock_sns = lazy_load(".sns", "mock_sns")
mock_sqs = lazy_load(".sqs", "mock_sqs")
mock_ssm = lazy_load(".ssm", "mock_ssm")
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 975d24440..6b6c934e2 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -143,6 +143,7 @@ backend_url_patterns = [
),
("ses", re.compile("https?://email\\.(.+)\\.amazonaws\\.com")),
("ses", re.compile("https?://ses\\.(.+)\\.amazonaws\\.com")),
+ ("signer", re.compile("https?://signer\\.(.+)\\.amazonaws\\.com")),
("sns", re.compile("https?://sns\\.(.+)\\.amazonaws\\.com")),
("sqs", re.compile("https?://(.*\\.)?(queue|sqs)\\.(.*\\.)?amazonaws\\.com")),
("ssm", re.compile("https?://ssm\\.(.+)\\.amazonaws\\.com")),
diff --git a/moto/signer/__init__.py b/moto/signer/__init__.py
new file mode 100644
index 000000000..2972ffddf
--- /dev/null
+++ b/moto/signer/__init__.py
@@ -0,0 +1,5 @@
+"""signer module initialization; sets value for base decorator."""
+from .models import signer_backends
+from ..core.models import base_decorator
+
+mock_signer = base_decorator(signer_backends)
diff --git a/moto/signer/exceptions.py b/moto/signer/exceptions.py
new file mode 100644
index 000000000..3d60ba7c8
--- /dev/null
+++ b/moto/signer/exceptions.py
@@ -0,0 +1 @@
+"""Exceptions raised by the signer service."""
diff --git a/moto/signer/models.py b/moto/signer/models.py
new file mode 100644
index 000000000..85d974cb2
--- /dev/null
+++ b/moto/signer/models.py
@@ -0,0 +1,192 @@
+from moto.core import BaseBackend, BaseModel
+from moto.core.utils import BackendDict, get_random_hex
+
+
+class SigningProfile(BaseModel):
+ def __init__(
+ self, account_id, region, name, platform_id, signature_validity_period, tags
+ ):
+ self.name = name
+ self.platform_id = platform_id
+ self.signature_validity_period = signature_validity_period or {
+ "value": 135,
+ "type": "MONTHS",
+ }
+ self.tags = tags
+
+ self.status = "Active"
+ self.arn = f"arn:aws:signer:{region}:{account_id}:/signing-profiles/{name}"
+ self.profile_version = get_random_hex(10)
+ self.profile_version_arn = f"{self.arn}/{self.profile_version}"
+
+ def cancel(self):
+ self.status = "Canceled"
+
+ def to_dict(self, full=True):
+ small = {
+ "arn": self.arn,
+ "profileVersion": self.profile_version,
+ "profileVersionArn": self.profile_version_arn,
+ }
+ if full:
+ small.update(
+ {
+ "status": self.status,
+ "profileName": self.name,
+ "platformId": self.platform_id,
+ "signatureValidityPeriod": self.signature_validity_period,
+ "signingMaterial": {},
+ "platformDisplayName": next(
+ (
+ p["displayName"]
+ for p in SignerBackend.platforms
+ if p["platformId"] == self.platform_id
+ ),
+ None,
+ ),
+ }
+ )
+ if self.tags:
+ small.update({"tags": self.tags})
+ return small
+
+
+class SignerBackend(BaseBackend):
+ """Implementation of signer APIs."""
+
+ platforms = [
+ {
+ "platformId": "AWSIoTDeviceManagement-SHA256-ECDSA",
+ "displayName": "AWS IoT Device Management SHA256-ECDSA ",
+ "partner": "AWSIoTDeviceManagement",
+ "target": "SHA256-ECDSA",
+ "category": "AWS",
+ "signingConfiguration": {
+ "encryptionAlgorithmOptions": {
+ "allowedValues": ["ECDSA"],
+ "defaultValue": "ECDSA",
+ },
+ "hashAlgorithmOptions": {
+ "allowedValues": ["SHA256"],
+ "defaultValue": "SHA256",
+ },
+ },
+ "signingImageFormat": {
+ "supportedFormats": ["JSONDetached"],
+ "defaultFormat": "JSONDetached",
+ },
+ "maxSizeInMB": 2048,
+ "revocationSupported": False,
+ },
+ {
+ "platformId": "AWSLambda-SHA384-ECDSA",
+ "displayName": "AWS Lambda",
+ "partner": "AWSLambda",
+ "target": "SHA384-ECDSA",
+ "category": "AWS",
+ "signingConfiguration": {
+ "encryptionAlgorithmOptions": {
+ "allowedValues": ["ECDSA"],
+ "defaultValue": "ECDSA",
+ },
+ "hashAlgorithmOptions": {
+ "allowedValues": ["SHA384"],
+ "defaultValue": "SHA384",
+ },
+ },
+ "signingImageFormat": {
+ "supportedFormats": ["JSONDetached"],
+ "defaultFormat": "JSONDetached",
+ },
+ "maxSizeInMB": 250,
+ "revocationSupported": True,
+ },
+ {
+ "platformId": "AmazonFreeRTOS-TI-CC3220SF",
+ "displayName": "Amazon FreeRTOS SHA1-RSA CC3220SF-Format",
+ "partner": "AmazonFreeRTOS",
+ "target": "SHA1-RSA-TISHA1",
+ "category": "AWS",
+ "signingConfiguration": {
+ "encryptionAlgorithmOptions": {
+ "allowedValues": ["RSA"],
+ "defaultValue": "RSA",
+ },
+ "hashAlgorithmOptions": {
+ "allowedValues": ["SHA1"],
+ "defaultValue": "SHA1",
+ },
+ },
+ "signingImageFormat": {
+ "supportedFormats": ["JSONEmbedded"],
+ "defaultFormat": "JSONEmbedded",
+ },
+ "maxSizeInMB": 16,
+ "revocationSupported": False,
+ },
+ {
+ "platformId": "AmazonFreeRTOS-Default",
+ "displayName": "Amazon FreeRTOS SHA256-ECDSA",
+ "partner": "AmazonFreeRTOS",
+ "target": "SHA256-ECDSA",
+ "category": "AWS",
+ "signingConfiguration": {
+ "encryptionAlgorithmOptions": {
+ "allowedValues": ["ECDSA", "RSA"],
+ "defaultValue": "ECDSA",
+ },
+ "hashAlgorithmOptions": {
+ "allowedValues": ["SHA256"],
+ "defaultValue": "SHA256",
+ },
+ },
+ "signingImageFormat": {
+ "supportedFormats": ["JSONEmbedded"],
+ "defaultFormat": "JSONEmbedded",
+ },
+ "maxSizeInMB": 16,
+ "revocationSupported": False,
+ },
+ ]
+
+ def __init__(self, region_name, account_id):
+ super().__init__(region_name, account_id)
+ self.signing_profiles: [str, SigningProfile] = dict()
+
+ def cancel_signing_profile(self, profile_name) -> None:
+ self.signing_profiles[profile_name].cancel()
+
+ def get_signing_profile(self, profile_name) -> SigningProfile:
+ return self.signing_profiles[profile_name]
+
+ def put_signing_profile(
+ self,
+ profile_name,
+ signature_validity_period,
+ platform_id,
+ tags,
+ ) -> SigningProfile:
+ """
+ The following parameters are not yet implemented: SigningMaterial, Overrides, SigningParamaters
+ """
+ profile = SigningProfile(
+ account_id=self.account_id,
+ region=self.region_name,
+ name=profile_name,
+ platform_id=platform_id,
+ signature_validity_period=signature_validity_period,
+ tags=tags,
+ )
+ self.signing_profiles[profile_name] = profile
+ return profile
+
+ def list_signing_platforms(self):
+ """
+ Pagination is not yet implemented. The parameters category, partner, target are not yet implemented
+ """
+ return SignerBackend.platforms
+
+
+# Using the lambda-regions
+# boto3.Session().get_available_regions("signer") still returns an empty list
+signer_backends: [str, [str, SignerBackend]] = BackendDict(SignerBackend, "lambda")
diff --git a/moto/signer/responses.py b/moto/signer/responses.py
new file mode 100644
index 000000000..ad42aa587
--- /dev/null
+++ b/moto/signer/responses.py
@@ -0,0 +1,43 @@
+"""Handles incoming signer requests, invokes methods, returns responses."""
+import json
+
+from moto.core.responses import BaseResponse
+from .models import signer_backends, SignerBackend
+
+
+class signerResponse(BaseResponse):
+ def __init__(self):
+ super().__init__(service_name="signer")
+
+ @property
+ def signer_backend(self) -> SignerBackend:
+ """Return backend instance specific for this region."""
+ return signer_backends[self.current_account][self.region]
+
+ def cancel_signing_profile(self):
+ profile_name = self.path.split("/")[-1]
+ self.signer_backend.cancel_signing_profile(profile_name=profile_name)
+ return "{}"
+
+ def get_signing_profile(self):
+ profile_name = self.path.split("/")[-1]
+ profile = self.signer_backend.get_signing_profile(profile_name=profile_name)
+ return json.dumps(profile.to_dict())
+
+ def put_signing_profile(self):
+ params = json.loads(self.body)
+ profile_name = self.path.split("/")[-1]
+ signature_validity_period = params.get("signatureValidityPeriod")
+ platform_id = params.get("platformId")
+ tags = params.get("tags")
+ profile = self.signer_backend.put_signing_profile(
+ profile_name=profile_name,
+ signature_validity_period=signature_validity_period,
+ platform_id=platform_id,
+ tags=tags,
+ )
+ return json.dumps(profile.to_dict(full=False))
+
+ def list_signing_platforms(self):
+ platforms = self.signer_backend.list_signing_platforms()
+ return json.dumps(dict(platforms=platforms))
diff --git a/moto/signer/urls.py b/moto/signer/urls.py
new file mode 100644
index 000000000..4b67c84ed
--- /dev/null
+++ b/moto/signer/urls.py
@@ -0,0 +1,15 @@
+"""signer base URL and path."""
+from .responses import signerResponse
+
+url_bases = [
+ r"https?://signer\.(.+)\.amazonaws\.com",
+]
+
+
+response = signerResponse()
+
+
+url_paths = {
+ "{0}/signing-profiles/(?P[^/]+)$": response.dispatch,
+ "{0}/signing-platforms$": response.dispatch,
+}
diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt
index 262ba0f30..3a577cd5d 100644
--- a/tests/terraformtests/terraform-tests.success.txt
+++ b/tests/terraformtests/terraform-tests.success.txt
@@ -207,6 +207,13 @@ servicediscovery:
- TestAccServiceDiscoveryPrivateDNSNamespace
- TestAccServiceDiscoveryPublicDNSNamespace
- TestAccServiceDiscoveryService
+signer:
+ - TestAccSignerSigningProfileDataSource_basic
+ - TestAccSignerSigningProfile_basic
+ - TestAccSignerSigningProfile_generateNameWithNamePrefix
+ - TestAccSignerSigningProfile_generateName
+ - TestAccSignerSigningProfile_tags
+ - TestAccSignerSigningProfile_signatureValidityPeriod
sns:
- TestAccSNSTopicPolicy
- TestAccSNSTopicDataSource
diff --git a/tests/test_signer/__init__.py b/tests/test_signer/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_signer/test_signing_platforms.py b/tests/test_signer/test_signing_platforms.py
new file mode 100644
index 000000000..1000be347
--- /dev/null
+++ b/tests/test_signer/test_signing_platforms.py
@@ -0,0 +1,21 @@
+"""Unit tests for signer-supported APIs."""
+import boto3
+
+import sure # noqa # pylint: disable=unused-import
+from moto import mock_signer
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_signer
+def test_list_signing_platforms():
+ client = boto3.client("signer", region_name="us-east-2")
+ resp = client.list_signing_platforms()
+
+ resp.should.have.key("platforms").should.have.length_of(4)
+
+ partners = [x["partner"] for x in resp["platforms"]]
+ set(partners).should.equal(
+ {"AmazonFreeRTOS", "AWSLambda", "AWSIoTDeviceManagement"}
+ )
diff --git a/tests/test_signer/test_signing_profiles.py b/tests/test_signer/test_signing_profiles.py
new file mode 100644
index 000000000..e7d90ab06
--- /dev/null
+++ b/tests/test_signer/test_signing_profiles.py
@@ -0,0 +1,70 @@
+"""Unit tests for signer-supported APIs."""
+import boto3
+
+import sure # noqa # pylint: disable=unused-import
+from moto import mock_signer
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_signer
+def test_put_signing_profile():
+ client = boto3.client("signer", region_name="eu-west-1")
+ resp = client.put_signing_profile(profileName="prof1", platformId="pid")
+
+ resp.should.have.key("arn")
+ resp.should.have.key("profileVersion")
+ resp.should.have.key("profileVersionArn")
+
+
+@mock_signer
+def test_get_signing_profile():
+ client = boto3.client("signer", region_name="eu-west-1")
+ resp = client.put_signing_profile(
+ profileName="prof1", platformId="AWSLambda-SHA384-ECDSA"
+ )
+
+ resp = client.get_signing_profile(profileName="prof1")
+
+ resp.should.have.key("arn")
+ resp.should.have.key("profileVersion")
+ resp.should.have.key("profileVersionArn")
+ resp.should.have.key("status").equals("Active")
+ resp.should.have.key("profileName").equals("prof1")
+ resp.should.have.key("platformId").equals("AWSLambda-SHA384-ECDSA")
+ resp.should.have.key("signatureValidityPeriod").equals(
+ {"type": "MONTHS", "value": 135}
+ )
+
+
+@mock_signer
+def test_get_signing_profile__with_args():
+ client = boto3.client("signer", region_name="eu-west-1")
+ resp = client.put_signing_profile(
+ profileName="prof1",
+ platformId="AWSLambda-SHA384-ECDSA",
+ signatureValidityPeriod={"type": "DAYS", "value": 10},
+ tags={"k1": "v1", "k2": "v2"},
+ )
+
+ resp = client.get_signing_profile(profileName="prof1")
+
+ resp.should.have.key("signatureValidityPeriod").equals(
+ {"type": "DAYS", "value": 10}
+ )
+ resp.should.have.key("tags").equals({"k1": "v1", "k2": "v2"})
+
+
+@mock_signer
+def test_cancel_signing_profile():
+ client = boto3.client("signer", region_name="eu-west-1")
+ resp = client.put_signing_profile(
+ profileName="prof1", platformId="AWSLambda-SHA384-ECDSA"
+ )
+
+ client.cancel_signing_profile(profileName="prof1")
+
+ resp = client.get_signing_profile(profileName="prof1")
+
+ resp.should.have.key("status").equals("Canceled")