Skip to content

Commit 5d8bf17

Browse files
Create the Python Track 2 SDK for the Microsoft Azure Attestation Service. (Azure#18023)
Initial preview release of Python Track 2 SDK for Attestation service. Co-authored-by: Sean Kane <68240067+seankane-msft@users.noreply.github.com>
1 parent 5fecd66 commit 5d8bf17

File tree

77 files changed

+4719
-853
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+4719
-853
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
/sdk/applicationinsights/azure-applicationinsights/ @divya-jay @geneh @alongafni
3232
/sdk/loganalytics/azure-loganalytics/ @divya-jay @geneh @alongafni
3333

34+
/sdk/attestation/azure-security-attestation @larryosterman @anilba06 @Azure/azure-sdk-write-attestation
35+
3436
# PRLabel: %Batch
3537
/sdk/batch/ @cRui861 @paterasMSFT @dpwatrous @gingi @zfengms
3638
/sdk/cognitiveservices/azure-cognitiveservices-vision-customvision/ @areddish

eng/.docsettings.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ known_content_issues:
8888
- ['sdk/purview/azure-purview-scanning/swagger/README.md',  '#4554']
8989
- ['sdk/containerregistry/azure-containerregistry/swagger/README.md', '#4554']
9090
- ['sdk/appconfiguration/azure-appconfiguration/swagger/README.md', '#4554']
91+
- ['sdk/attestation/azure-security-attestation/swagger/README.md', '#4554']
9192

9293
# common.
9394
- ['sdk/appconfiguration/azure-appconfiguration/README.md', 'common']

sdk/attestation/azure-security-attestation/CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
# Release History
22

3-
## 1.0.0b2 (Unreleased)
3+
## 1.0.0b2 (2021-05-11)
44

5+
### Features Added
6+
7+
- Preliminary implementation of a Track 2 SDK for the attestation service.
8+
9+
### Breaking Changes
10+
11+
- Complete reimplementation of the API surface, follows the API patterns already
12+
established for the attestation service.
513

614
## 1.0.0b1 (2021-01-15)
715

sdk/attestation/azure-security-attestation/README.md

Lines changed: 332 additions & 23 deletions
Large diffs are not rendered by default.

sdk/attestation/azure-security-attestation/azure/security/attestation/__init__.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,47 @@
22
# --------------------------------------------------------------------------
33
# Copyright (c) Microsoft Corporation. All rights reserved.
44
# Licensed under the MIT License. See License.txt in the project root for license information.
5-
# Code generated by Microsoft (R) AutoRest Code Generator.
6-
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
75
# --------------------------------------------------------------------------
86

9-
from ._attestation_client import AttestationClient
7+
from ._client import AttestationClient
8+
from ._administration_client import AttestationAdministrationClient
9+
from ._models import (AttestationResponse,
10+
AttestationSigner,
11+
AttestationToken,
12+
AttestationSigningKey,
13+
AttestationData,
14+
PolicyResult,
15+
AttestationResult,
16+
TpmAttestationResponse,
17+
TpmAttestationRequest,
18+
AttestationTokenValidationException,
19+
PolicyCertificatesModificationResult,
20+
AttestationType,
21+
StoredAttestationPolicy,
22+
CertificateModification)
23+
from ._configuration import TokenValidationOptions
1024
from ._version import VERSION
1125

1226
__version__ = VERSION
13-
__all__ = ['AttestationClient']
27+
__all__ = [
28+
'AttestationClient',
29+
'AttestationAdministrationClient',
30+
'AttestationType',
31+
'AttestationToken',
32+
'AttestationSigner',
33+
'AttestationResponse',
34+
'AttestationResult',
35+
'AttestationData',
36+
'TokenValidationOptions',
37+
'StoredAttestationPolicy',
38+
'PolicyResult',
39+
'CertificateModification',
40+
'AttestationSigningKey',
41+
'TpmAttestationRequest',
42+
'TpmAttestationResponse',
43+
'PolicyCertificatesModificationResult',
44+
'AttestationTokenValidationException',
45+
]
1446

1547
try:
1648
from ._patch import patch_sdk # type: ignore
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for license information.
5+
# --------------------------------------------------------------------------
6+
7+
from typing import List, Any, Optional, TYPE_CHECKING
8+
9+
from azure.core import PipelineClient
10+
from msrest import Deserializer, Serializer
11+
from six import python_2_unicode_compatible
12+
13+
if TYPE_CHECKING:
14+
# pylint: disable=unused-import,ungrouped-imports
15+
from typing import Any
16+
17+
from azure.core.credentials import TokenCredential
18+
from azure.core.pipeline.transport import HttpRequest, HttpResponse
19+
20+
from ._generated import AzureAttestationRestClient
21+
from ._generated.models import AttestationType, PolicyResult, PolicyCertificatesResult, JSONWebKey, AttestationCertificateManagementBody, PolicyCertificatesModificationResult as GeneratedPolicyCertificatesModificationResult
22+
from ._configuration import AttestationClientConfiguration
23+
from ._models import AttestationSigner, AttestationToken, AttestationResponse, StoredAttestationPolicy, AttestationSigningKey, PolicyCertificatesModificationResult
24+
from ._common import Base64Url
25+
import cryptography
26+
import cryptography.x509
27+
import base64
28+
from azure.core.tracing.decorator import distributed_trace
29+
from threading import Lock, Thread
30+
31+
32+
class AttestationAdministrationClient(object):
33+
"""Provides administrative APIs for managing an instance of the Attestation Service.
34+
35+
:param str instance_url: base url of the service
36+
:param credential: Credentials for the caller used to interact with the service.
37+
:type credential: azure.core.credentials.TokenCredential
38+
:keyword Pipeline pipeline: If omitted, the standard pipeline is used.
39+
:keyword HttpTransport transport: If omitted, the standard pipeline is used.
40+
:keyword list[HTTPPolicy] policies: If omitted, the standard pipeline is used.
41+
"""
42+
43+
def __init__(
44+
self,
45+
credential, # type: "TokenCredential"
46+
instance_url, # type: str
47+
**kwargs # type: Any
48+
):
49+
# type: (...) -> None
50+
if not credential:
51+
raise ValueError("Missing credential.")
52+
self._config = AttestationClientConfiguration(credential, instance_url, **kwargs)
53+
self._client = AzureAttestationRestClient(credential, instance_url, **kwargs)
54+
self._statelock = Lock()
55+
self._signing_certificates = None
56+
57+
@distributed_trace
58+
def get_policy(self, attestation_type, **kwargs):
59+
#type(AttestationType, **Any) -> AttestationResult[str]:
60+
""" Retrieves the attestation policy for a specified attestation type.
61+
62+
:param azure.security.attestation.AttestationType attestation_type: :class:`azure.security.attestation.AttestationType` for
63+
which to retrieve the policy.
64+
:return AttestationResponse[str]: Attestation service response encapsulating a string attestation policy.
65+
66+
"""
67+
68+
policyResult = self._client.policy.get(attestation_type, **kwargs)
69+
token = AttestationToken[PolicyResult](token=policyResult.token, body_type=PolicyResult)
70+
token_body = token.get_body()
71+
stored_policy = AttestationToken[StoredAttestationPolicy](token=token_body.policy, body_type=StoredAttestationPolicy)
72+
73+
actual_policy = stored_policy.get_body().attestation_policy #type: bytes
74+
75+
if self._config.token_validation_options.validate_token:
76+
token.validate_token(self._config.token_validation_options, self._get_signers(**kwargs))
77+
78+
return AttestationResponse[str](token, actual_policy.decode('utf-8'))
79+
80+
@distributed_trace
81+
def set_policy(self, attestation_type, attestation_policy, signing_key=None, **kwargs):
82+
#type:(AttestationType, str, Optional[AttestationSigningKey], **Any) -> AttestationResponse[PolicyResult]
83+
""" Sets the attestation policy for the specified attestation type.
84+
85+
:param azure.security.attestation.AttestationType attestation_type: :class:`azure.security.attestation.AttestationType` for
86+
which to set the policy.
87+
:param str attestation_policy: Attestation policy to be set.
88+
:param Optional[AttestationSigningKey] signing_key: Optional signing key to be
89+
used to sign the policy before sending it to the service.
90+
:return AttestationResponse[PolicyResult]: Attestation service response encapsulating a :class:`PolicyResult`.
91+
92+
.. note::
93+
If the attestation instance is in *Isolated* mode, then the
94+
`signing_key` parameter MUST be a signing key containing one of the
95+
certificates returned by :meth:`get_policy_management_certificates`.
96+
97+
If the attestation instance is in *AAD* mode, then the `signing_key`
98+
parameter does not need to be provided.
99+
"""
100+
policy_token = AttestationToken[StoredAttestationPolicy](
101+
body=StoredAttestationPolicy(attestation_policy = attestation_policy.encode('ascii')),
102+
signer=signing_key,
103+
body_type=StoredAttestationPolicy)
104+
policyResult = self._client.policy.set(attestation_type=attestation_type, new_attestation_policy=policy_token.serialize(), **kwargs)
105+
token = AttestationToken[PolicyResult](token=policyResult.token,
106+
body_type=PolicyResult)
107+
if self._config.token_validation_options.validate_token:
108+
if not token.validate_token(self._config.token_validation_options, self._get_signers(**kwargs)):
109+
raise Exception("Token Validation of PolicySet API failed.")
110+
111+
112+
return AttestationResponse[PolicyResult](token, token.get_body())
113+
114+
@distributed_trace
115+
def get_policy_management_certificates(self, **kwargs):
116+
#type:(**Any) -> AttestationResponse[list[list[bytes]]]
117+
""" Retrieves the set of policy management certificates for the instance.
118+
119+
The list of policy management certificates will only be non-empty if the
120+
attestation service instance is in Isolated mode.
121+
122+
:return AttestationResponse[list[list[bytes]]: Attestation service response
123+
encapsulating a list of DER encoded X.509 certificate chains.
124+
"""
125+
126+
cert_response = self._client.policy_certificates.get(**kwargs)
127+
token = AttestationToken[PolicyCertificatesResult](
128+
token=cert_response.token,
129+
body_type=PolicyCertificatesResult)
130+
if self._config.token_validation_options.validate_token:
131+
if not token.validate_token(self._config.token_validation_options, self._get_signers(**kwargs)):
132+
raise Exception("Token Validation of PolicyCertificates API failed.")
133+
certificates = []
134+
135+
cert_list = token.get_body()
136+
137+
for key in cert_list.policy_certificates.keys:
138+
key_certs = [base64.b64decode(cert) for cert in key.x5_c]
139+
certificates.append(key_certs)
140+
return AttestationResponse(token, certificates)
141+
142+
@distributed_trace
143+
def add_policy_management_certificate(self, certificate_to_add, signing_key, **kwargs):
144+
#type:(bytes, AttestationSigningKey, **Any)-> AttestationResponse[PolicyCertificatesModificationResult]
145+
""" Adds a new policy management certificate to the set of policy management certificates for the instance.
146+
147+
:param bytes certificate_to_add: DER encoded X.509 certificate to add to
148+
the list of attestation policy management certificates.
149+
:param AttestationSigningKey signing_key: Signing Key representing one of
150+
the *existing* attestation signing certificates.
151+
:return AttestationResponse[PolicyCertificatesModificationResult]: Attestation service response
152+
encapsulating the status of the add request.
153+
154+
The :class:`PolicyCertificatesModificationResult` response to the
155+
:meth:`add_policy_management_certificate` API contains two attributes
156+
of interest.
157+
158+
The first is `certificate_resolution`, which indicates
159+
whether the certificate in question is present in the set of policy
160+
management certificates after the operation has completed, or if it is
161+
absent.
162+
163+
The second is the `thumbprint` of the certificate added. The `thumbprint`
164+
for the certificate is the SHA1 hash of the DER encoding of the
165+
certificate.
166+
167+
"""
168+
key=JSONWebKey(kty='RSA', x5_c = [ base64.b64encode(certificate_to_add).decode('ascii')])
169+
add_body = AttestationCertificateManagementBody(policy_certificate=key)
170+
cert_add_token = AttestationToken[AttestationCertificateManagementBody](
171+
body=add_body,
172+
signer=signing_key,
173+
body_type=AttestationCertificateManagementBody)
174+
175+
cert_response = self._client.policy_certificates.add(cert_add_token.serialize(), **kwargs)
176+
token = AttestationToken[GeneratedPolicyCertificatesModificationResult](token=cert_response.token,
177+
body_type=GeneratedPolicyCertificatesModificationResult)
178+
if self._config.token_validation_options.validate_token:
179+
if not token.validate_token(self._config.token_validation_options, self._get_signers(**kwargs)):
180+
raise Exception("Token Validation of PolicyCertificate Add API failed.")
181+
return AttestationResponse[PolicyCertificatesModificationResult](token, PolicyCertificatesModificationResult._from_generated(token.get_body()))
182+
183+
@distributed_trace
184+
def remove_policy_management_certificate(self, certificate_to_add, signing_key, **kwargs):
185+
#type:(bytes, AttestationSigningKey, **Any)-> AttestationResponse[PolicyCertificatesModificationResult]
186+
""" Removes a new policy management certificate to the set of policy management certificates for the instance.
187+
188+
:param bytes certificate_to_add: DER encoded X.509 certificate to add to
189+
the list of attestation policy management certificates.
190+
:param AttestationSigningKey signing_key: Signing Key representing one of
191+
the *existing* attestation signing certificates.
192+
:return AttestationResponse[PolicyCertificatesModificationResult]: Attestation service response
193+
encapsulating a list of DER encoded X.509 certificate chains.
194+
195+
The :class:`PolicyCertificatesModificationResult` response to the
196+
:meth:`remove_policy_management_certificate` API contains two attributes
197+
of interest.
198+
199+
The first is `certificate_resolution`, which indicates
200+
whether the certificate in question is present in the set of policy
201+
management certificates after the operation has completed, or if it is
202+
absent.
203+
204+
The second is the `thumbprint` of the certificate added. The `thumbprint`
205+
for the certificate is the SHA1 hash of the DER encoding of the
206+
certificate.
207+
208+
"""
209+
key=JSONWebKey(kty='RSA', x5_c = [ base64.b64encode(certificate_to_add).decode('ascii')])
210+
add_body = AttestationCertificateManagementBody(policy_certificate=key)
211+
cert_add_token = AttestationToken[AttestationCertificateManagementBody](
212+
body=add_body,
213+
signer=signing_key,
214+
body_type=AttestationCertificateManagementBody)
215+
216+
cert_response = self._client.policy_certificates.remove(cert_add_token.serialize(), **kwargs)
217+
token = AttestationToken[GeneratedPolicyCertificatesModificationResult](token=cert_response.token,
218+
body_type=GeneratedPolicyCertificatesModificationResult)
219+
if self._config.token_validation_options.validate_token:
220+
if not token.validate_token(self._config.token_validation_options, self._get_signers(**kwargs)):
221+
raise Exception("Token Validation of PolicyCertificate Remove API failed.")
222+
return AttestationResponse[PolicyCertificatesModificationResult](token, PolicyCertificatesModificationResult._from_generated(token.get_body()))
223+
224+
def _get_signers(self, **kwargs):
225+
#type(**Any) -> List[AttestationSigner]
226+
""" Returns the set of signing certificates used to sign attestation tokens.
227+
"""
228+
229+
with self._statelock:
230+
if (self._signing_certificates == None):
231+
signing_certificates = self._client.signing_certificates.get(**kwargs)
232+
self._signing_certificates = []
233+
for key in signing_certificates.keys:
234+
# Convert the returned certificate chain into an array of X.509 Certificates.
235+
certificates = []
236+
for x5c in key.x5_c:
237+
der_cert = base64.b64decode(x5c)
238+
certificates.append(der_cert)
239+
self._signing_certificates.append(AttestationSigner(certificates, key.kid))
240+
signers = self._signing_certificates
241+
return signers
242+
243+
def close(self):
244+
# type: () -> None
245+
self._client.close()
246+
247+
def __enter__(self):
248+
# type: () -> AttestationAdministrationClient
249+
self._client.__enter__()
250+
return self
251+
252+
def __exit__(self, *exc_details):
253+
# type: (Any) -> None
254+
self._client.__exit__(*exc_details)

0 commit comments

Comments
 (0)