Parse pKIOverlapPeriod from LDIF export into days

166 Views Asked by At

Some background to what I'm trying to achieve (you don't need to read it if you're not interested, just for reference).

I have exported a certificate template from AD into an LDIF-file using the following command:

ldifde -m -v -d "CN=MyTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=domain,DC=com" -f MyTemplate.ldf

The template contains the following record:

pKIOverlapPeriod:: AICmCv/e//8=

It seems like this is a base64-encoded Windows filetime structure, possibly with some sort of encoding on top(?).

From Microsoft's website

The FILETIME structure is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since January 1, 1601, Coordinated Universal Time (UTC).

I tried to parse it into hex and got 0080a60affdeffff. However, I want to parse it into something like "6 weeks" or "2 years".

Thus, I wrote a Python program to parse the LDIF and convert the pKIOverlapPeriod but I don't get the expected output.

The actual output is:

pKIOverlapPeriod:
  unit: days
  value: 41911

Since I have configured the overlap to be "6 weeks" in the certificate template, this is the output I expect:

pKIOverlapPeriod:
  unit: days
  value: 42

The Python code I use looks like this:

# pip3 install ldif pyyaml
from ldif import LDIFParser
import os
import sys
import json
import yaml

# Converts a Win32 FILETIME structure to a dictionary.
def filetime_to_dict(filetime):
    # This variable is supposed to contain the number of 100-nanosecond intervals since January 1, 1601...
    intervals = int.from_bytes(filetime, byteorder = 'big')
    return {
        "unit": "days",
        "value": int(intervals // (1E7 * 60 * 60 * 24))
    }

parser = LDIFParser(open(os.path.join(os.getcwd(), sys.argv[1]), "rb"))
for dn, records in parser.parse():
    template = {}
    for key in records:
        # Special magic for pKIOverlapPeriod goes here
        if key == 'pKIOverlapPeriod':
            template[key] = filetime_to_dict(records[key][0])
            continue
        # end of magic
        if len(records[key]) == 1:
            template[key] = records[key][0]
        else:
            template[key] = records[key]
    data = yaml.dump(
        yaml.load(
            json.dumps(template, default = str),
            Loader = yaml.SafeLoader),
        default_flow_style = False)
    print(data)

The LDIF looks like this:

dn: CN=AteaComputer,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=atea,DC=se
changetype: add
cn: AteaComputer
displayName: Atea Computer
distinguishedName: 
 CN=AteaComputer,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN
 =Configuration,DC=atea,DC=se
dSCorePropagationData: 20220601093015.0Z
dSCorePropagationData: 20220518190731.0Z
dSCorePropagationData: 16010101000000.0Z
flags: 131680
instanceType: 4
msPKI-Cert-Template-OID: 
 1.3.6.1.4.1.311.21.8.12474934.3506392.5459122.6785906.4016631.21.8298576.73677
 34
msPKI-Certificate-Application-Policy: 1.3.6.1.5.5.7.3.1
msPKI-Certificate-Application-Policy: 1.3.6.1.5.5.7.3.2
msPKI-Certificate-Name-Flag: 134217728
msPKI-Enrollment-Flag: 32
msPKI-Minimal-Key-Size: 256
msPKI-Private-Key-Flag: 101056512
msPKI-RA-Application-Policies: 
 msPKI-Asymmetric-Algorithm`PZPWSTR`ECDH_P256`msPKI-Hash-Algorithm`PZPWSTR`SHA2
 56`msPKI-Key-Usage`DWORD`16777215`msPKI-Symmetric-Algorithm`PZPWSTR`3DES`msPKI
 -Symmetric-Key-Length`DWORD`168`
msPKI-RA-Signature: 0
msPKI-Template-Minor-Revision: 1
msPKI-Template-Schema-Version: 4
name: AteaComputer
objectCategory: 
 CN=PKI-Certificate-Template,CN=Schema,CN=Configuration,DC=atea,DC=se
objectClass: top
objectClass: pKICertificateTemplate
pKICriticalExtensions: 2.5.29.15
pKIDefaultKeySpec: 1
pKIExpirationPeriod:: AEA5hy7h/v8=
pKIExtendedKeyUsage: 1.3.6.1.5.5.7.3.1
pKIExtendedKeyUsage: 1.3.6.1.5.5.7.3.2
pKIKeyUsage:: iA==
pKIMaxIssuingDepth: 0
pKIOverlapPeriod:: AICmCv/e//8=
revision: 104
showInAdvancedViewOnly: TRUE
uSNChanged: 53271
uSNCreated: 28782
whenChanged: 20220601093015.0Z
whenCreated: 20220518190731.0Z

What did I do wrong? I have cross-checked my implementation against e.g. Python's winfiletime, with the same result, so I'm starting to suspect that the bytes need to be decoded before I can convert it into an int.

1

There are 1 best solutions below

0
user1938742 On BEST ANSWER

After fiddling around with this I came up with the following:

def filetime_to_dict(filetime):
    input = 18446744073709551616 - int.from_bytes(filetime, byteorder = 'little')
    if intervals % (1E7 * 60 * 60 * 24 * 365) == 0:
        return {
            "unit": "years",
            "value": int(intervals / (1E7 * 60 * 60 * 24 * 365))
        }
    if intervals % (1E7 * 60 * 60 * 24 * 30) == 0:
        return {
            "unit": "months",
            "value": int(intervals / (1E7 * 60 * 60 * 24 * 30))
        }
    if intervals % (1E7 * 60 * 60 * 24 * 7) == 0:
        return {
        "unit": "weeks",
        "value": int(intervals / (1E7 * 60 * 60 * 24 * 7))
    }
    if intervals % (1E7 * 60 * 60 * 24) == 0:
        return {
            "unit": "days",
            "value": int(intervals / (1E7 * 60 * 60 * 24))
        }
    if intervals % (1E7 * 60 * 60) == 0:
        return {
            "unit": "hours",
            "value": int(intervals / (1E7 * 60 * 60))
        }
    return {
        "unit": "filetime",
        "value": filetime
    }