Skip to content

Commit 3d0f237

Browse files
authored
CodeTransparency SDK support to API version 2025-01-31-preview and added new verification logic (Azure#48627)
* initial SDK generation
1 parent c2a9a19 commit 3d0f237

File tree

79 files changed

+1696
-6573
lines changed

Some content is hidden

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

79 files changed

+1696
-6573
lines changed

.vscode/cspell.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,11 @@
353353
"cbor",
354354
"ccfs",
355355
"cose",
356-
"scitt"
356+
"Jwks",
357+
"PHDR",
358+
"PKIX",
359+
"scitt",
360+
"tstr"
357361
]
358362
},
359363
{

sdk/confidentialledger/Azure.Security.CodeTransparency/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Release History
22

3+
## 1.0.0-beta.4 (2025-03-27)
4+
5+
### Features Added
6+
7+
- Aligned with the latest changes (Feb 25) of the IETF draft: https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/
8+
- Updated receipt verification logic.
9+
10+
### Breaking Changes
11+
12+
### Bugs Fixed
13+
14+
### Other Changes
15+
316
## 1.0.0-beta.3 (Unreleased)
417

518
### Features Added

sdk/confidentialledger/Azure.Security.CodeTransparency/README.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dotnet add package Azure.Security.CodeTransparency --prerelease
2222

2323
- A running and accessible Code Transparency Service
2424
- Ability to create `COSE_Sign1` envelopes, [an example script][CTS_claim_generator_script]
25-
- Your signer details (CA cert or DID issuer) have to be configured in the running service, [about available configuration][CTS_configuration_doc]
25+
- Your signer details (CA cert) have to be configured in the running service, [about available configuration][CTS_configuration_doc]
2626
- You can get a valid Bearer token if the service authentication is configured to require one, [see example](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/confidentialledger/Azure.Security.CodeTransparency/samples/Sample3_UseYourCredentials.md)
2727

2828
### Thread safety
@@ -42,21 +42,34 @@ Before submitting the cose file, the service must be configured with the relevan
4242
To submit the signature, use the following code:
4343

4444
```C# Snippet:CodeTransparencySubmission
45-
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"), null);
45+
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"));
4646
FileStream fileStream = File.OpenRead("signature.cose");
4747
BinaryData content = BinaryData.FromStream(fileStream);
48-
Operation<GetOperationResult> operation = await client.CreateEntryAsync(content);
49-
Response<GetOperationResult> operationResult = await operation.WaitForCompletionAsync();
50-
Console.WriteLine($"The entry id to use to get the entry and receipt is {{{operationResult.Value.EntryId}}}");
51-
Response<BinaryData> signatureWithReceiptResponse = await client.GetEntryAsync(operationResult.Value.EntryId, true);
52-
BinaryData signatureWithReceipt = signatureWithReceiptResponse.Value;
53-
byte[] signatureWithReceiptBytes = signatureWithReceipt.ToArray();
48+
Operation<BinaryData> operation = await client.CreateEntryAsync(WaitUntil.Started, content);
5449
```
5550

5651
Once you have the receipt and the signature, you can verify whether the signature was actually included in the Code Transparency service by running the receipt verification logic. The verifier checks if the receipt was issued for a given signature and if the receipt signature was endorsed by the service.
5752

53+
```C# Snippet:CodeTransparencyVerifyReceipt
54+
Response<JwksDocument> key = client.GetPublicKeys();
55+
56+
CcfReceiptVerifier.VerifyTransparentStatementReceipt(key.Value.Keys[0], receiptBytes, inputSignedPayloadBytes);
57+
```
58+
59+
Alternatively, you can retrieve the Transparent Statement of the corresponding submission and run the receipt verification logic
60+
5861
```C# Snippet:CodeTransparencyVerification
59-
CcfReceiptVerifier.RunVerification(signatureWithReceiptBytes);
62+
Response<BinaryData> transparentStatement = client.GetEntryStatement(entryId);
63+
byte[] transparentStatementBytes = transparentStatement.Value.ToArray();
64+
65+
try
66+
{
67+
client.RunTransparentStatementVerification(transparentStatementBytes);
68+
}
69+
catch (Exception e)
70+
{
71+
Console.WriteLine(e.Message);
72+
}
6073
```
6174

6275
If the verification completes without exception, you can trust the signature and the receipt. This allows you to safely inspect the contents of the files, especially the contents of the payload embedded in a cose signature envelope.
@@ -94,8 +107,8 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
94107

95108
<!-- LINKS -->
96109
[COSE_RFC]: https://www.rfc-editor.org/rfc/rfc8152.txt
97-
[SCITT_ARCHITECTURE_RFC]: https://www.ietf.org/archive/id/draft-ietf-scitt-architecture-01.txt
98-
[SCITT_RECEIPT_RFC]: https://www.ietf.org/archive/id/draft-birkholz-scitt-receipts-03.txt
110+
[SCITT_ARCHITECTURE_RFC]: https://www.ietf.org/archive/id/draft-ietf-scitt-architecture-11.txt
111+
[SCITT_RECEIPT_RFC]: https://www.ietf.org/archive/id/draft-ietf-cose-merkle-tree-proofs-08.txt
99112
[API_reference]: https://learn.microsoft.com/dotnet/api/azure.security.keyvault.keys
100113
[Service_source_code]: https://github.com/microsoft/scitt-ccf-ledger
101114
[CTS_claim_generator_script]: https://github.com/microsoft/scitt-ccf-ledger/tree/main/demo/cts_poc

sdk/confidentialledger/Azure.Security.CodeTransparency/api/Azure.Security.CodeTransparency.net8.0.cs

Lines changed: 57 additions & 301 deletions
Large diffs are not rendered by default.

sdk/confidentialledger/Azure.Security.CodeTransparency/api/Azure.Security.CodeTransparency.netstandard2.0.cs

Lines changed: 57 additions & 301 deletions
Large diffs are not rendered by default.

sdk/confidentialledger/Azure.Security.CodeTransparency/samples/Sample1_HelloWorld.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ Then, you will also need to have a valid `COSE_Sign1` envelope file. There are m
1313
To create a new `CodeTransparencyClient` that will interact with the service, without explicit credentials if the service allows it or if you
1414
want to get the publicly accessible data only. Then use a subclient to work with entries:
1515

16-
```C# Snippet:CodeTransparencySample1_CreateClient
17-
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"), null);
16+
```C# Snippet:CodeTransparencySample_CreateClient
17+
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"));
1818
```
1919

2020
## Submit the file
2121

2222
The most basic usage is to submit a valid signature file to the service. Acceptance of the submission is a long running operation which is why the response will contain the operation id.
2323

24-
```C# Snippet:CodeTransparencySample1_SendSignature
24+
```C# Snippet:CodeTransparencySubmission
25+
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"));
2526
FileStream fileStream = File.OpenRead("signature.cose");
2627
BinaryData content = BinaryData.FromStream(fileStream);
27-
Operation<GetOperationResult> operation = await client.CreateEntryAsync(content);
28+
Operation<BinaryData> operation = await client.CreateEntryAsync(WaitUntil.Started, content);
2829
```
2930

3031
## Verify if operation was successful
@@ -34,7 +35,21 @@ If you want to be sure that the submission completed successfully it is necessar
3435
Another important part of the operation check is that it will contain an identifier (entry ID) to be used to get the transaction receipt.
3536

3637
```C# Snippet:CodeTransparencySample1_WaitForResult
37-
Response<GetOperationResult> response = await operation.WaitForCompletionAsync();
38-
GetOperationResult value = response.Value;
39-
Console.WriteLine($"The entry id to use to get the entry and receipt is {{{value.EntryId}}}");
38+
Response<BinaryData> operationResult = await operation.WaitForCompletionAsync();
39+
40+
string entryId = string.Empty;
41+
CborReader cborReader = new CborReader(operationResult.Value);
42+
cborReader.ReadStartMap();
43+
while (cborReader.PeekState() != CborReaderState.EndMap)
44+
{
45+
string key = cborReader.ReadTextString();
46+
if (key == "EntryId")
47+
{
48+
entryId = cborReader.ReadTextString();
49+
}
50+
else
51+
cborReader.SkipValue();
52+
}
53+
54+
Console.WriteLine($"The entry id to use to get the receipt and Transparent Statement is {{{entryId}}}");
4055
```

sdk/confidentialledger/Azure.Security.CodeTransparency/samples/Sample2_ReceiptDownloadVerification.md

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ To get started, you'll need a url of the service.
1212
To create a new `CodeTransparencyClient` that will interact with the service, without explicit credentials if the service allows it or if you
1313
want to get the publicly accessible data only. Then use a subclient to work with entries:
1414

15-
```C# Snippet:CodeTransparencySample2_CreateClient
16-
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"), null);
15+
```C# Snippet:CodeTransparencySample_CreateClient
16+
CodeTransparencyClient client = new(new Uri("https://<< service name >>.confidential-ledger.azure.com"));
1717
```
1818

1919
## Download receipt
@@ -24,16 +24,16 @@ The receipt on its own contains only the inclusion proof and the signature. You
2424

2525
The easiest way is to download the receipt and the signature together which was stored after the submission. The receipt will be added to the unprotected header of the signature.
2626

27-
```C# Snippet:CodeTransparencySample2_GetEntryWithEmbeddedReceipt
28-
Response<BinaryData> signatureWithReceipt = await client.GetEntryAsync("2.34", true);
27+
```C# Snippet:CodeTransparencySample2_GetEntryStatement
28+
Response<BinaryData> signatureWithReceiptResponse = await client.GetEntryStatementAsync(entryId);
2929
```
3030

3131
### Raw receipt
3232

3333
If you have the signature as a separate file already then you can download the raw receipt file.
3434

3535
```C# Snippet:CodeTransparencySample2_GetRawReceipt
36-
Response<BinaryData> receipt = await client.GetEntryReceiptAsync("2.34");
36+
Response<BinaryData> receipt = await client.GetEntryAsync(entryId);
3737
```
3838

3939
## Verify
@@ -46,14 +46,33 @@ The following examples will use a default public key resolver to get the keys fo
4646

4747
When receipt is embedded in the signature then passing just the signature is enough.
4848

49-
```C# Snippet:CodeTransparencySample2_VerifyEntryWithEmbeddedReceipt
50-
CcfReceiptVerifier.RunVerification(signatureWithReceipt.Value.ToArray());
49+
```C# Snippet:CodeTransparencyVerification
50+
Response<BinaryData> transparentStatement = client.GetEntryStatement(entryId);
51+
byte[] transparentStatementBytes = transparentStatement.Value.ToArray();
52+
53+
try
54+
{
55+
client.RunTransparentStatementVerification(transparentStatementBytes);
56+
}
57+
catch (Exception e)
58+
{
59+
Console.WriteLine(e.Message);
60+
}
5161
```
5262

53-
### Raw receipt
54-
55-
If the receipt is a separate file then it needs to be passed as a second argument next to the signature.
56-
57-
```C# Snippet:CodeTransparencySample2_VerifyEntryAndReceipt
58-
CcfReceiptVerifier.RunVerification(receipt.Value.ToArray(), signatureBytes);
59-
```
63+
Alternatively, you can provide your own JsonWebKey, the receipt and the corresponding signed claims
64+
65+
```C# Snippet:CodeTransparencySample2_VerifyReceiptAndInputSignedStatement
66+
// Create a JsonWebKey
67+
JsonWebKey jsonWebKey = new JsonWebKey(<.....>);
68+
byte[] inputSignedStatement = readFileBytes("<input_signed_claims");
69+
70+
try
71+
{
72+
CcfReceiptVerifier.VerifyTransparentStatementReceipt(jsonWebKey, signatureWithReceiptBytes, inputSignedStatement);
73+
}
74+
catch (Exception e)
75+
{
76+
Console.WriteLine(e.Message);
77+
}
78+
```

sdk/confidentialledger/Azure.Security.CodeTransparency/samples/Sample4_FindYourReceipt.md

Lines changed: 0 additions & 48 deletions
This file was deleted.

sdk/confidentialledger/Azure.Security.CodeTransparency/src/Azure.Security.CodeTransparency.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
<ItemGroup>
1212
<Compile Include="$(AzureCoreSharedSources)AzureResourceProviderNamespaceAttribute.cs" LinkBase="Shared/Core" />
13+
<Compile Include="$(AzureCoreSharedSources)Base64Url.cs" LinkBase="Shared/Core" />
1314
<Compile Include="$(AzureCoreSharedSources)AzureKeyCredentialPolicy.cs" LinkBase="Shared" />
1415
<Compile Include="$(AzureCoreSharedSources)LightweightPkcs8Decoder.cs" LinkBase="Shared" />
1516
<Compile Include="$(AzureCoreSharedSources)PemReader.cs" LinkBase="Shared" />
1617
</ItemGroup>
1718

1819
<ItemGroup>
1920
<PackageReference Include="Azure.Core" />
21+
<PackageReference Include="Azure.Security.KeyVault.Keys" />
2022
<PackageReference Include="System.ClientModel" />
2123
<PackageReference Include="System.Text.Json" />
2224
<PackageReference Include="System.Security.Cryptography.Cose" />

sdk/confidentialledger/Azure.Security.CodeTransparency/src/CodeTransparencyCertificateClient.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,23 @@ protected CodeTransparencyCertificateClient()
3333
}
3434

3535
/// <summary> Initializes a new instance of CertificateClient.</summary>
36-
/// <param name="certificateEndpoint"> The Identity Service URL. </param>
37-
/// <exception cref="ArgumentNullException"> <paramref name="certificateEndpoint"/> </exception>
38-
public CodeTransparencyCertificateClient(Uri certificateEndpoint) : this(certificateEndpoint, new CodeTransparencyClientOptions())
36+
/// <param name="endpoint"> The Identity Service URL. </param>
37+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> </exception>
38+
public CodeTransparencyCertificateClient(Uri endpoint) : this(endpoint, new CodeTransparencyClientOptions())
3939
{
4040
}
4141

4242
/// <summary> Initializes a new instance of CertificateClient.</summary>
43-
/// <param name="certificateEndpoint"> The Identity Service URL. </param>
43+
/// <param name="endpoint"> The Identity Service URL. </param>
4444
/// <param name="options"> The options for configuring the client. </param>
45-
/// <exception cref="ArgumentNullException"> <paramref name="certificateEndpoint"/> </exception>
46-
public CodeTransparencyCertificateClient(Uri certificateEndpoint, CodeTransparencyClientOptions options)
45+
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> </exception>
46+
public CodeTransparencyCertificateClient(Uri endpoint, CodeTransparencyClientOptions options)
4747
{
48-
Argument.AssertNotNull(certificateEndpoint, nameof(certificateEndpoint));
48+
Argument.AssertNotNull(endpoint, nameof(endpoint));
4949
Argument.AssertNotNull(options, nameof(options));
5050
ClientDiagnostics = new(options, true);
5151
_pipeline = HttpPipelineBuilder.Build(options, Array.Empty<HttpPipelinePolicy>(), Array.Empty<HttpPipelinePolicy>(), new ResponseClassifier());
52-
_certificateEndpoint = certificateEndpoint;
52+
_certificateEndpoint = endpoint;
5353
_results = new ConcurrentDictionary<string, ServiceIdentityResult>();
5454
_cacheTTLSec = options.CacheTTLSeconds;
5555
}

0 commit comments

Comments
 (0)