102 lines
3.6 KiB
PowerShell
102 lines
3.6 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
$credentialTarget = 'D3ROVoice-Android-Upload-20260821'
|
|
$keystorePath = 'C:\Users\encep\.d3ro\release\d3ro-upload-key-20260821.p12'
|
|
$expectedAlias = 'd3ro-upload-20260821'
|
|
$releaseIdentity = Get-Content -Raw -LiteralPath 'release\android-release-identity.json' | ConvertFrom-Json
|
|
$expectedSha256 = $releaseIdentity.uploadCertificateSha256
|
|
|
|
if (-not (Test-Path -LiteralPath $keystorePath -PathType Leaf)) {
|
|
throw "Android upload keystore is missing: $keystorePath"
|
|
}
|
|
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
|
|
public static class D3ROCredentialReader
|
|
{
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
public struct Credential
|
|
{
|
|
public UInt32 Flags;
|
|
public UInt32 Type;
|
|
public string TargetName;
|
|
public string Comment;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
|
|
public UInt32 CredentialBlobSize;
|
|
public IntPtr CredentialBlob;
|
|
public UInt32 Persist;
|
|
public UInt32 AttributeCount;
|
|
public IntPtr Attributes;
|
|
public string TargetAlias;
|
|
public string UserName;
|
|
}
|
|
|
|
[DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
public static extern bool CredRead(string target, UInt32 type, UInt32 flags, out IntPtr credential);
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
public static extern void CredFree(IntPtr credential);
|
|
}
|
|
'@
|
|
|
|
$credentialPointer = [IntPtr]::Zero
|
|
$password = $null
|
|
try {
|
|
if (-not [D3ROCredentialReader]::CredRead($credentialTarget, 1, 0, [ref]$credentialPointer)) {
|
|
$errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
|
throw "Windows credential is unavailable: $credentialTarget (Win32 $errorCode)"
|
|
}
|
|
|
|
$credential = [Runtime.InteropServices.Marshal]::PtrToStructure(
|
|
$credentialPointer,
|
|
[type][D3ROCredentialReader+Credential]
|
|
)
|
|
if ($credential.CredentialBlobSize -eq 0) {
|
|
throw "Windows credential has an empty secret: $credentialTarget"
|
|
}
|
|
|
|
$blob = [byte[]]::new($credential.CredentialBlobSize)
|
|
[Runtime.InteropServices.Marshal]::Copy($credential.CredentialBlob, $blob, 0, $blob.Length)
|
|
$password = [Text.Encoding]::Unicode.GetString($blob).TrimEnd([char]0)
|
|
if ($password.Length -lt 20) {
|
|
throw 'Stored Android upload key password fails the minimum length policy.'
|
|
}
|
|
|
|
$env:D3RO_UPLOAD_KEY_PASSWORD_CHECK = $password
|
|
$keytoolOutput = & keytool.exe -list -v `
|
|
-storetype PKCS12 `
|
|
-keystore $keystorePath `
|
|
-storepass:env D3RO_UPLOAD_KEY_PASSWORD_CHECK 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "keytool could not read the Android upload keystore: $keytoolOutput"
|
|
}
|
|
$rendered = $keytoolOutput -join "`n"
|
|
if ($rendered -notmatch [regex]::Escape($expectedAlias)) {
|
|
throw "Android upload key alias mismatch. Expected $expectedAlias."
|
|
}
|
|
if ($rendered -notmatch 'PrivateKeyEntry') {
|
|
throw 'Android upload keystore does not contain a private key entry.'
|
|
}
|
|
if ($rendered -notmatch [regex]::Escape($expectedSha256)) {
|
|
throw "Android upload certificate mismatch. Expected $expectedSha256."
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
ok = $true
|
|
alias = $expectedAlias
|
|
certificateSha256 = $expectedSha256
|
|
keystoreBytes = (Get-Item -LiteralPath $keystorePath).Length
|
|
privateKeyReadable = $true
|
|
credentialTargetPresent = $true
|
|
} | ConvertTo-Json
|
|
}
|
|
finally {
|
|
Remove-Item Env:D3RO_UPLOAD_KEY_PASSWORD_CHECK -ErrorAction SilentlyContinue
|
|
$password = $null
|
|
if ($credentialPointer -ne [IntPtr]::Zero) {
|
|
[D3ROCredentialReader]::CredFree($credentialPointer)
|
|
}
|
|
}
|