Skip to content

Commit 8ab05e6

Browse files
committed
[WIP] More implementation
1 parent f97911a commit 8ab05e6

File tree

11 files changed

+335
-85
lines changed

11 files changed

+335
-85
lines changed

ci/release.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,22 @@ stages:
8787
displayName: 'Install Nuget'
8888

8989
- powershell: |
90-
nuget install python -Version 3.14.3 -x -noninteractive -o host_python
90+
nuget install python -Version 3.14.4 -x -noninteractive -o host_python
9191
$py = Get-Item host_python\python\tools
9292
Write-Host "Adding $py to PATH"
9393
Write-Host "##vso[task.prependpath]$py"
94-
displayName: Set up Python 3.14.3
94+
displayName: Set up Python 3.14.4
9595
workingDirectory: $(Build.BinariesDirectory)
9696
9797
- powershell: >
9898
python -c "import sys;
9999
print(sys.version);
100100
print(sys.executable);
101-
sys.exit(0 if sys.version_info[:5] == (3, 14, 3, 'final', 0) else 1)"
102-
displayName: Check Python version is 3.14.3
101+
sys.exit(0 if sys.version_info[:5] == (3, 14, 4, 'final', 0) else 1)"
102+
displayName: Check Python version is 3.14.4
103103
104104
- powershell: |
105-
python -m pip install "pymsbuild>=1.2.0b1"
105+
python -m pip install "pymsbuild==1.2.2"
106106
displayName: 'Install build dependencies'
107107
108108
- ${{ if eq(parameters.PreTest, 'true') }}:
@@ -357,13 +357,13 @@ stages:
357357
$hashes = $files | `
358358
Sort-Object Name | `
359359
Format-Table Name, @{
360-
Label="MD5";
361-
Expression={(Get-FileHash $_ -Algorithm MD5).Hash}
360+
Label="SHA256";
361+
Expression={(Get-FileHash $_ -Algorithm SHA256).Hash}
362362
}, Length -AutoSize | `
363363
Out-String -Width 4096
364364
$hashes
365365
workingDirectory: $(DIST_DIR)
366-
displayName: 'Generate hashes (MD5)'
366+
displayName: 'Generate hashes (SHA256)'
367367
368368
- ${{ if eq(parameters.Publish, 'true') }}:
369369
- ${{ if eq(parameters.Sign, 'true') }}:
@@ -374,7 +374,7 @@ stages:
374374
displayName: 'Download PuTTY key'
375375

376376
- powershell: |
377-
git clone https://github.com/python/cpython-bin-deps --branch putty --single-branch --depth 1 --progress -v "putty"
377+
git clone https://github.com/python/cpython-bin-deps --revision 9f9e6fc31a55406ee5ff0198ea47bbb445eeb942 --depth 1 --progress -v "putty"
378378
"##vso[task.prependpath]$(gi putty)"
379379
workingDirectory: $(Pipeline.Workspace)
380380
displayName: 'Download PuTTY binaries'

ci/repartition-index.yml

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ parameters:
1313
type: boolean
1414
default: false
1515
- name: TestPublish
16-
displayName: "Run all steps without publishing"
16+
displayName: "Run all steps (including signing) without publishing"
1717
type: boolean
1818
default: false
1919

@@ -29,6 +29,8 @@ stages:
2929

3030
variables:
3131
- group: PythonOrgPublish
32+
- ${{ if or(eq(parameters.Publish, 'true'), eq(parameters.TestPublish, 'true')) }}:
33+
- group: CPythonSign
3234

3335
steps:
3436
- checkout: self
@@ -50,6 +52,45 @@ stages:
5052
displayName: 'Repartition index'
5153
workingDirectory: $(Build.BinariesDirectory)
5254
55+
- ${{ if or(eq(parameters.Publish, 'true'), eq(parameters.TestPublish, 'true')) }}:
56+
- powershell: |
57+
git clone https://github.com/python/cpython-bin-deps --revision fb06137dccc43ed5b030cdd9e3560990b37f39da --depth 1 --progress -v "signtool"
58+
"##vso[task.setvariable variable=MAKECAT]$(gi signtool\x64\makecat.exe)"
59+
"##vso[task.setvariable variable=SIGNTOOL]$(gi signtool\x64\signtool.exe)"
60+
"##vso[task.setvariable variable=DLIB]$(gi signtool\azure_trusted_signing\x64\Azure.CodeSigning.Dlib.dll)"
61+
workingDirectory: $(Pipeline.Workspace)
62+
displayName: 'Download signtool binaries'
63+
64+
- task: AzureCLI@2
65+
displayName: 'Azure Login (1/2)'
66+
inputs:
67+
azureSubscription: 'Python Signing'
68+
scriptType: 'ps'
69+
scriptLocation: 'inlineScript'
70+
inlineScript: |
71+
"##vso[task.setvariable variable=AZURE_CLIENT_ID;issecret=true]${env:servicePrincipalId}"
72+
"##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]${env:idToken}"
73+
"##vso[task.setvariable variable=AZURE_TENANT_ID;issecret=true]${env:tenantId}"
74+
addSpnToEnvironment: true
75+
76+
- powershell: >
77+
az login --service-principal
78+
-u $(AZURE_CLIENT_ID)
79+
--tenant $(AZURE_TENANT_ID)
80+
--allow-no-subscriptions
81+
--federated-token $(AZURE_ID_TOKEN)
82+
displayName: 'Azure Login (2/2)'
83+
84+
- powershell: |
85+
python "$(Build.SourcesDirectory)\ci\sign-json.py" (dir *.json)
86+
displayName: 'Sign index files'
87+
workingDirectory: $(Build.BinariesDirectory)
88+
env:
89+
SIGN_TOOLS: $(Pipeline.Workspace)\sign_tools
90+
TRUSTED_SIGNING_URI: $(TrustedSigningUri)
91+
TRUSTED_SIGNING_ACCOUNT: $(TrustedSigningAccount)
92+
TRUSTED_SIGNING_CERTIFICATE_NAME: $(TrustedSigningCertificateName)
93+
5394
- publish: $(Build.BinariesDirectory)\index
5495
artifact: index
5596
displayName: Publish index artifact
@@ -63,7 +104,7 @@ stages:
63104
displayName: 'Download PuTTY key'
64105

65106
- powershell: |
66-
git clone https://github.com/python/cpython-bin-deps --branch putty --single-branch --depth 1 --progress -v "putty"
107+
git clone https://github.com/python/cpython-bin-deps --revision 9f9e6fc31a55406ee5ff0198ea47bbb445eeb942 --depth 1 --progress -v "putty"
67108
"##vso[task.prependpath]$(gi putty)"
68109
workingDirectory: $(Pipeline.Workspace)
69110
displayName: 'Download PuTTY binaries'

ci/sign-json.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
except KeyError:
1515
TOOLS = Path("_sign_tools").absolute()
1616

17+
1718
def download_tool(url, name):
1819
dest = TOOLS / name
1920
dest.mkdir(parents=True, exist_ok=True)
@@ -34,7 +35,10 @@ def download_tool(url, name):
3435
with open(dest / f, "wb") as f2:
3536
f2.write(zf.read(f))
3637

37-
def find_tool(pattern, url):
38+
39+
def find_tool(env, pattern, url):
40+
if os.getenv(env):
41+
return Path(os.environ[env])
3842
tools = list(TOOLS.glob(pattern))
3943
if tools:
4044
return tools[-1]
@@ -46,15 +50,19 @@ def find_tool(pattern, url):
4650
print("Failed to install tool for", pattern.replace("/", "\\").rpartition("\\")[-1])
4751
sys.exit(1)
4852

53+
4954
SIGNTOOL = find_tool(
55+
"SIGNTOOL",
5056
"sign/bin/*/x64/signtool.exe",
5157
"https://www.nuget.org/api/v2/package/Microsoft.Windows.SDK.BuildTools/10.0.28000.1721",
5258
)
5359
MAKECAT = find_tool(
60+
"MAKECAT",
5461
"sign/bin/*/x64/makecat.exe",
5562
None,
5663
)
5764
DLIB = find_tool(
65+
"DLIB",
5866
"dlib/bin/x64/Azure.CodeSigning.Dlib.dll",
5967
"https://www.nuget.org/api/v2/package/Microsoft.ArtifactSigning.Client/1.0.128",
6068
)
@@ -64,6 +72,7 @@ def find_tool(pattern, url):
6472
print("makecat:", MAKECAT)
6573
print("dlib:", DLIB)
6674

75+
6776
AAS_DATA = {
6877
"Endpoint": os.environ["TRUSTED_SIGNING_URI"],
6978
"CodeSigningAccountName": os.environ["TRUSTED_SIGNING_ACCOUNT"],
@@ -74,18 +83,20 @@ def find_tool(pattern, url):
7483
"SharedTokenCacheCredential",
7584
"VisualStudioCredential",
7685
"VisualStudioCodeCredential",
77-
"AzureCliCredential",
7886
"AzurePowerShellCredential",
7987
"AzureDeveloperCliCredential",
8088
"InteractiveBrowserCredential"
8189
]
8290
}
8391

92+
8493
with open(TOOLS / "metadata.json", "w", encoding="utf-8") as f:
8594
json.dump(AAS_DATA, f, indent=2)
8695

96+
8797
CAT = Path.cwd() / (Path(sys.argv[1]).stem + ".cat")
8898

99+
89100
with open(TOOLS / "files.cdf", "w", encoding="ansi") as f:
90101
print("[CatalogHeader]", file=f)
91102
print("Name=", CAT.name, sep="", file=f)
@@ -99,18 +110,22 @@ def find_tool(pattern, url):
99110
for a in map(Path, sys.argv[1:]):
100111
print("<HASH>", a.name, "=", a.absolute(), sep="", file=f)
101112

113+
102114
if CAT.is_file():
103115
CAT.unlink()
104116

117+
105118
args = [MAKECAT, "-v", TOOLS / "files.cdf"]
106119
print("##[command]", end="")
107120
print(*args)
108121
run(args)
109122

123+
110124
if not CAT.is_file():
111125
print("Failed to create catalog.")
112126
sys.exit(2)
113127

128+
114129
args = [
115130
SIGNTOOL, "sign",
116131
"/v",
@@ -122,6 +137,7 @@ def find_tool(pattern, url):
122137
CAT
123138
]
124139

140+
125141
print("##[command]", end="")
126142
print(*args)
127143
run(args)

src/_native/verify_trust.cpp

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,145 @@ extern "C" {
1414
}
1515

1616

17-
static bool cert_subject_matches(PCCERT_CONTEXT pCert, const wchar_t *expected)
17+
static std::vector<BYTE> nameinfo_from_blob(CERT_NAME_BLOB *name)
1818
{
19-
if (!pCert || !expected || !*expected) {
20-
return true;
19+
DWORD cb = 0;
20+
if (!CryptDecodeObjectEx(
21+
X509_ASN_ENCODING,
22+
X509_NAME,
23+
name->pbData,
24+
name->cbData,
25+
0,
26+
nullptr,
27+
nullptr,
28+
&cb
29+
) || cb == 0) {
30+
return {};
31+
}
32+
33+
std::vector<BYTE> buf(cb);
34+
if (!CryptDecodeObjectEx(
35+
X509_ASN_ENCODING,
36+
X509_NAME,
37+
name->pbData,
38+
name->cbData,
39+
0,
40+
nullptr,
41+
buf.data(),
42+
&cb
43+
)) {
44+
return {};
2145
}
46+
buf.resize(cb);
47+
return buf;
48+
}
2249

50+
51+
static std::vector<BYTE> nameinfo_from_name(const wchar_t *name)
52+
{
2353
DWORD encoded_size = 0;
2454

25-
if (!CertStrToNameW(X509_ASN_ENCODING, expected, CERT_X500_NAME_STR,
55+
if (!CertStrToNameW(X509_ASN_ENCODING, name, CERT_X500_NAME_STR,
2656
nullptr, nullptr, &encoded_size, nullptr)) {
27-
return false;
57+
return {};
2858
}
2959

3060
std::vector<BYTE> encoded(encoded_size);
3161

32-
if (!CertStrToNameW(X509_ASN_ENCODING, expected, CERT_X500_NAME_STR,
62+
if (!CertStrToNameW(X509_ASN_ENCODING, name, CERT_X500_NAME_STR,
3363
nullptr, encoded.data(), &encoded_size, nullptr)) {
34-
return false;
64+
return {};
3565
}
3666

3767
CERT_NAME_BLOB expected_name = {};
3868
expected_name.cbData = encoded_size;
3969
expected_name.pbData = encoded.data();
4070

41-
return CertCompareCertificateName(X509_ASN_ENCODING, &pCert->pCertInfo->Subject, &expected_name);
71+
return nameinfo_from_blob(&expected_name);
72+
}
73+
74+
75+
static std::wstring read_rdn_attr(CERT_RDN_ATTR &attr) {
76+
DWORD cb = CertRDNValueToStrW(attr.dwValueType, &attr.Value, nullptr, 0);
77+
if (cb > 1024) {
78+
return {};
79+
}
80+
std::wstring v(cb, L'\0');
81+
v.resize(CertRDNValueToStrW(attr.dwValueType, &attr.Value, v.data(), cb));
82+
return v;
4283
}
4384

4485

86+
static bool cert_subject_matches(PCCERT_CONTEXT pCert, const wchar_t *expected)
87+
{
88+
if (!pCert || !expected || !*expected) {
89+
return true;
90+
}
91+
92+
auto expected_info_buf = nameinfo_from_name(expected);
93+
auto actual_info_buf = nameinfo_from_blob(&pCert->pCertInfo->Subject);
94+
95+
if (expected_info_buf.empty() || actual_info_buf.empty()) {
96+
return false;
97+
}
98+
99+
const CERT_NAME_INFO *expected_info =
100+
reinterpret_cast<const CERT_NAME_INFO *>(expected_info_buf.data());
101+
const CERT_NAME_INFO *actual_info =
102+
reinterpret_cast<const CERT_NAME_INFO *>(actual_info_buf.data());
103+
104+
// Turn constraints into a vector of (oid, value) pairs.
105+
// Each pair must be present in the actual name.
106+
std::vector<std::pair<std::string, std::wstring>> expected_pairs;
107+
expected_pairs.reserve(8);
108+
109+
for (DWORD i = 0; i < expected_info->cRDN; ++i) {
110+
CERT_RDN &rdn = expected_info->rgRDN[i];
111+
for (DWORD j = 0; j < rdn.cRDNAttr; ++j) {
112+
CERT_RDN_ATTR &attr = rdn.rgRDNAttr[j];
113+
if (!attr.pszObjId) {
114+
return false;
115+
}
116+
117+
auto v = read_rdn_attr(attr);
118+
if (!v.empty()) {
119+
expected_pairs.emplace_back(std::string(attr.pszObjId), std::move(v));
120+
}
121+
}
122+
}
123+
124+
// For each expected (OID, value), find an identical (OID, value) in the actual name.
125+
for (const auto &need : expected_pairs) {
126+
const std::string &oid = need.first;
127+
const std::wstring &expect = need.second;
128+
129+
bool found = false;
130+
131+
for (DWORD i = 0; i < actual_info->cRDN && !found; ++i) {
132+
CERT_RDN &rdn = actual_info->rgRDN[i];
133+
for (DWORD j = 0; j < rdn.cRDNAttr; ++j) {
134+
CERT_RDN_ATTR &attr = rdn.rgRDNAttr[j];
135+
if (!attr.pszObjId || oid != attr.pszObjId) {
136+
continue;
137+
}
138+
139+
auto v = read_rdn_attr(attr);
140+
141+
if (CompareStringOrdinal(v.c_str(), -1, expect.c_str(), -1, TRUE) == CSTR_EQUAL) {
142+
found = true;
143+
break;
144+
}
145+
}
146+
}
147+
148+
if (!found) {
149+
return false;
150+
}
151+
}
152+
153+
return true;
154+
}
155+
45156
static bool resolve_eku_to_oid_utf8(const wchar_t *eku_in, std::string &oid_out) {
46157
oid_out.clear();
47158
if (!eku_in || !*eku_in) {

0 commit comments

Comments
 (0)