Public/ConvertFrom-KeytabJson.ps1
|
<#
SPDX-License-Identifier: Apache-2.0 Copyright (c) 2025 Stefan Ploch #> function ConvertFrom-KeytabJson { <# .SYNOPSIS Convert canonical JSON back into a keytab file (requires key bytes). .DESCRIPTION Reads canonical JSON as produced by ConvertTo-KeytabJson -RevealKeys and reconstructs a MIT v0x0502 keytab. Requires key bytes to be present in JSON. Can restrict ACL on output and supports deterministic timestamps for reproducible builds. .PARAMETER JsonPath Path to the canonical JSON file. .PARAMETER OutputPath Output keytab path to write. Defaults to <JsonPath>.keytab when not specified. .PARAMETER Force Overwrite OutputPath if it exists. .PARAMETER FixedTimestampUtc Use a fixed timestamp for written entries for deterministic output. .PARAMETER RestrictAcl Apply a user-only ACL on the output file. .INPUTS System.String (file path) or objects with FilePath/FullName properties. .OUTPUTS System.String. Returns the OutputPath written. .EXAMPLE ConvertFrom-KeytabJson -JsonPath .\entry.json -OutputPath .\out.keytab -Force Reconstruct a keytab from JSON, overwriting the destination if present. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position=0)] [Alias('FullName','FilePath','PSPath')] [ValidateNotNullOrEmpty()] [string]$JsonPath, [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position=1)] [Alias('OutFile', 'Out')] [string]$OutputPath, [switch]$Force, [datetime]$FixedTimestampUtc, [switch]$RestrictAcl ) begin { $in = Resolve-PathUniversal -Path $JsonPath -Purpose Input if ($OutputPath) { $out = Resolve-PathUniversal -Path $OutputPath -Purpose Output } else { $out = Resolve-OutputPath -InputPath $in -Extension '.keytab' -CreateDirectory } } process { $entries = Get-Content -LiteralPath $in -Raw | ConvertFrom-Json if (-not $entries) { throw "No entries found in JSON '$in'."} # Group into principal descriptors and key sets $byPrincipal = $entries | Group-Object { '{0}|{1}|{2}' -f $_.Realm, ($_.Components -join '/'), $_.NameType } $principalDescriptors = @() $keySetsByPrincipal = @() foreach ($g in $byPrincipal) { $first = $g.Group[0] $princDesc = [pscustomobject]@{ Components = @($first.Components) Realm = $first.Realm NameType = [int]$first.NameType Display = ('{0}@{1}' -f ($first.Components -join '/'), $first.Realm) } $principalDescriptors += $princDesc $kvGroups = $g.Group | Group-Object Kvno $keySetSets = foreach ($kv in $kvGroups) { $keys = @{} foreach ($entry in $kv.Group) { if ($null -eq $entry.Key) { throw "Key bytes missing in JSON; cannot rebuild keytab."} $keys[[int]$entry.Etype] = [byte[]]$entry.Key } [pscustomobject]@{ Kvno = [int]$kv.Name Keys = $keys Source = 'Json' RetrievedAt = (Get-Date).ToUniversalTime() } } $keySetsByPrincipal += $keySetSets } if ((Test-Path -LiteralPath $out) -and -not $Force) { throw "Output exists. Use -force to overwrite"} $tsArg = @{} if ($PSBoundParameters.ContainsKey('FixedTimestampUtc') -and $FixedTimestampUtc) { $tsArg.FixedTimestampUtc = $FixedTimestampUtc } if ($PSCmdlet.ShouldProcess($out, "Write keytab from JSON")) { New-KeytabFile -Path $out -PrincipalDescriptors $principalDescriptors -KeySets ($keySetsByPrincipal | Select-Object -First 1) -RestrictAcl:$RestrictAcl @tsArg } } end { return $out } } |