Skip to content

Commit f766856

Browse files
committed
Initial commit
0 parents  commit f766856

14 files changed

Lines changed: 1106 additions & 0 deletions

File tree

.gitignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
4+
# Distribution / packaging
5+
.Python
6+
build/
7+
develop-eggs/
8+
dist/
9+
downloads/
10+
eggs/
11+
.eggs/
12+
lib/
13+
lib64/
14+
parts/
15+
sdist/
16+
var/
17+
wheels/
18+
share/python-wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
MANIFEST
23+
24+
# pyenv / uv
25+
# For a library or package, you might want to ignore these files since the code is
26+
# intended to run in multiple environments; otherwise, check them in:
27+
.python-version
28+
29+
# Environments
30+
.env
31+
.venv
32+
env/
33+
venv/
34+
ENV/
35+
env.bak/
36+
venv.bak/
37+
38+
# PyCharm
39+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
40+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
41+
# and can be added to the global gitignore or merged into this file. For a more nuclear
42+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
43+
.idea/
44+
45+
# macOS
46+
.DS_Store
47+
default.profraw

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# regcertipy
2+
Parses cached certificate templates from a Windows Registry `.reg` file and
3+
displays them in the same style as
4+
[Certipy](https://github.com/ly4k/Certipy) does.
5+
6+
## Getting started
7+
We prefer using the [uv package manager](https://docs.astral.sh/uv/), as it
8+
will automatically create a virtual environment for you.
9+
10+
```
11+
$ uv venv
12+
$ uv pip install regcertipy
13+
$ regcertipy -h
14+
usage: regcertipy [-h] regfile
15+
16+
Regfile ingestor for Certipy
17+
18+
positional arguments:
19+
regfile Path to the .reg file.
20+
21+
options:
22+
-h, --help show this help message and exit
23+
```
24+
25+
Use regedit.exe to export the keys under
26+
`HKEY_USERS\.DEFAULT\Software\ Microsoft\Cryptography\CertificateTemplateCache\`.
27+
Then, the .reg file can be fed into regcertipy with: regcertipy <regfile>.
28+
29+
![Example of how to export a .reg file](resources/regedit.png)
30+
31+
## Development
32+
Note that we use the [Black code formatter](https://black.readthedocs.io/en/stable/)
33+
for code formatting. Moreover, we use the Git Flow branching model, meaning
34+
that we actively develop on the "develop" branch, and merge to the "main"
35+
branch (& tag it) when a new release is made, making the "main" branch the
36+
production branch.
37+
38+
```
39+
$ uv sync --dev # Also installs the Black code formatter.
40+
$ uv run black . # To format the current code base.
41+
$ uv run regcertipy -h
42+
usage: regcertipy [-h] regfile
43+
44+
Regfile ingestor for Certipy
45+
46+
positional arguments:
47+
regfile Path to the .reg file.
48+
49+
options:
50+
-h, --help show this help message and exit
51+
```
52+
53+
You can also run the `__init__.py` or `__main.py__` Python file in your
54+
favourite debugger.

bitbucket-pipelines.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
image: atlassian/default-image:3
2+
3+
definitions:
4+
steps:
5+
- step: &Test
6+
name: "Run unit tests"
7+
image: python:3.12
8+
script:
9+
- pip install -r requirements.txt
10+
- (cd src && python -m unittest)
11+
- step: &Lint
12+
name: "Run linting checks"
13+
image: python:3.12
14+
script:
15+
- pip install black
16+
- black --check src/
17+
- step: &Validate-Branch-For-Tag-Step
18+
name: Validating branch & tag combination
19+
script:
20+
- |
21+
echo "Fetching all the git remote references (such as tags)."
22+
git fetch origin "+refs/heads/*:refs/remotes/origin/*"
23+
24+
if [[ "$BITBUCKET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
25+
VALID_BRANCH="^.*remotes/origin/(release|hotfix)/.*$"
26+
DEPLOYMENT_ENVIRONMENT="rc"
27+
elif [[ "$BITBUCKET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
28+
VALID_BRANCH="^.*remotes/origin/(release|hotfix)/.*$"
29+
DEPLOYMENT_ENVIRONMENT="staging"
30+
elif [[ "$BITBUCKET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
31+
VALID_BRANCH="^.*remotes/origin/master$"
32+
DEPLOYMENT_ENVIRONMENT="production"
33+
else
34+
echo "Tag \"$BITBUCKET_TAG\" has an invalid format. Aborting..."
35+
exit -1
36+
fi
37+
38+
echo "Verifying that tag \"$BITBUCKET_TAG\" is applied on a branch matching \"$VALID_BRANCH\"."
39+
FOUND=false
40+
41+
TAG_BRANCHES=$(git branch -a --contains "$BITBUCKET_TAG")
42+
echo "Branches to which tag $BITBUCKET_TAG is applied: "
43+
echo $TAG_BRANCHES
44+
45+
while IFS= read -r BRANCH ; do
46+
NORMALIZED_BRANCH=$(echo $BRANCH | sed -E -e "s/(\* )|( )//")
47+
48+
if [ "$(echo $NORMALIZED_BRANCH | grep -cm1 -E "$VALID_BRANCH")" -eq 1 ]; then
49+
FOUND=true
50+
echo "Tag \"$BITBUCKET_TAG\" is applied to a valid branch: \"$NORMALIZED_BRANCH\"."
51+
break
52+
fi
53+
done <<< "$TAG_BRANCHES"
54+
55+
if ! $FOUND; then
56+
echo "Tag \"$BITBUCKET_TAG\" was not found on a branch matching \"$VALID_BRANCH\". Aborting..."
57+
exit -1
58+
fi
59+
60+
pipelines:
61+
default:
62+
- parallel:
63+
- step: *Test
64+
- step: *Lint
65+
branches:
66+
"release/*":
67+
- parallel:
68+
- step: *Test
69+
- step: *Lint
70+
tags:
71+
"{v*.*.*}":
72+
- step: *Validate-Branch-For-Tag-Step
73+
- parallel:
74+
- step: *Test
75+
- step: *Lint
76+
pull-requests:
77+
"*":
78+
- parallel:
79+
- step: *Test
80+
- step: *Lint

pyproject.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[project]
2+
name = "regcertipy"
3+
version = "1.0.0"
4+
description = """
5+
Parses cached certificate templates from a Windows Registry file and \
6+
displays them in the same style as Certipy does.
7+
"""
8+
readme = "README.md"
9+
authors = [
10+
{ name = "Max Grim", email = "max@outflank.nl" },
11+
{ name = "Cedric van Bockhaven", email = "cedric@outflank.nl" },
12+
]
13+
requires-python = ">=3.12"
14+
dependencies = [
15+
"certipy-ad==4.8.2",
16+
]
17+
18+
[project.scripts]
19+
regcertipy = "regcertipy:main"
20+
21+
[build-system]
22+
requires = ["hatchling"]
23+
build-backend = "hatchling.build"
24+
25+
[dependency-groups]
26+
dev = [
27+
"black>=25.1.0",
28+
]

resources/example.reg

218 KB
Binary file not shown.

resources/regedit.png

70.2 KB
Loading

src/regcertipy/__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import argparse
2+
3+
from certipy.lib.formatting import pretty_print
4+
from regcertipy.models import CertTemplate
5+
from regcertipy.parsers import RegfileParser
6+
7+
8+
def main():
9+
parser = argparse.ArgumentParser(
10+
add_help=True,
11+
description="Regfile ingestor for Certipy",
12+
formatter_class=argparse.RawDescriptionHelpFormatter,
13+
)
14+
parser.add_argument("regfile", help="Path to the .reg file.")
15+
args = parser.parse_args()
16+
17+
parser = RegfileParser(args.regfile)
18+
19+
templates = []
20+
21+
for key, dct in parser.to_dict().items():
22+
if not key.startswith(
23+
"HKEY_USERS\\.DEFAULT\\Software\\Microsoft"
24+
"\\Cryptography\\CertificateTemplateCache\\"
25+
):
26+
continue
27+
28+
name = key.split("\\")[-1]
29+
30+
template = CertTemplate(name, dct)
31+
templates.append(template)
32+
33+
print(f"[*] Found {len(templates)} templates in the registry")
34+
35+
for template in templates:
36+
pretty_print(template.to_dict())
37+
38+
39+
if __name__ == "__main__":
40+
main()

src/regcertipy/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import regcertipy
2+
3+
regcertipy.main()

src/regcertipy/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .cert_template import CertTemplate
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from typing import Dict
2+
3+
import regcertipy.utils
4+
from certipy.commands.find import filetime_to_str
5+
from certipy.lib.constants import (
6+
CERTIFICATE_RIGHTS,
7+
EXTENDED_RIGHTS_NAME_MAP,
8+
MS_PKI_CERTIFICATE_NAME_FLAG,
9+
MS_PKI_ENROLLMENT_FLAG,
10+
OID_TO_STR_MAP,
11+
)
12+
from certipy.lib.security import CertifcateSecurity
13+
14+
15+
class CertTemplate:
16+
def __init__(self, name: str, data: Dict):
17+
self.data = data
18+
19+
self.name = name
20+
self.display_name = self.data["DisplayName"]
21+
self.oid = (
22+
self.data["msPKI-Cert-Template-OID"].decode("utf-16-le").rstrip("\0\0")
23+
)
24+
self.validity_period = filetime_to_str(self.data["ValidityPeriod"])
25+
self.renewal_period = filetime_to_str(self.data["RenewalOverlap"])
26+
self.name_flags = MS_PKI_CERTIFICATE_NAME_FLAG(
27+
self.data["msPKI-Certificate-Name-Flag"]
28+
)
29+
30+
self.enrollment_flags = MS_PKI_ENROLLMENT_FLAG(
31+
self.data["msPKI-Enrollment-Flag"]
32+
)
33+
self.signatures_required = self.data["msPKI-RA-Signature"]
34+
35+
self.extended_key_usage = list(
36+
map(
37+
lambda x: OID_TO_STR_MAP[x] if x in OID_TO_STR_MAP else x,
38+
data["ExtKeyUsageSyntax"]
39+
.decode("utf-16-le")
40+
.rstrip("\0\0")
41+
.split("\0"),
42+
)
43+
)
44+
45+
self.permissions = self._build_permissions(self.data["Security"])
46+
47+
@staticmethod
48+
def _build_permissions(security_dict: Dict):
49+
security = CertifcateSecurity(security_dict)
50+
51+
enrollment_permissions = {}
52+
enrollment_rights = []
53+
all_extended_rights = []
54+
55+
permissions = {}
56+
57+
for sid, rights in security.aces.items():
58+
if (
59+
EXTENDED_RIGHTS_NAME_MAP["Enroll"] in rights["extended_rights"]
60+
or EXTENDED_RIGHTS_NAME_MAP["AutoEnroll"] in rights["extended_rights"]
61+
):
62+
enrollment_rights.append(regcertipy.utils.sid_to_name(sid))
63+
if (
64+
EXTENDED_RIGHTS_NAME_MAP["All-Extended-Rights"]
65+
in rights["extended_rights"]
66+
):
67+
all_extended_rights.append(regcertipy.utils.sid_to_name(sid))
68+
69+
if len(enrollment_rights) > 0:
70+
enrollment_permissions["Enrollment Rights"] = enrollment_rights
71+
72+
if len(all_extended_rights) > 0:
73+
enrollment_permissions["All Extended Rights"] = all_extended_rights
74+
75+
if len(enrollment_permissions) > 0:
76+
permissions["Enrollment Permissions"] = enrollment_permissions
77+
78+
object_control_permissions = {"Owner": security.owner}
79+
80+
rights_mapping = [
81+
(CERTIFICATE_RIGHTS.GENERIC_ALL, [], "Full Control Principals"),
82+
(CERTIFICATE_RIGHTS.WRITE_OWNER, [], "Write Owner Principals"),
83+
(CERTIFICATE_RIGHTS.WRITE_DACL, [], "Write Dacl Principals"),
84+
(
85+
CERTIFICATE_RIGHTS.WRITE_PROPERTY,
86+
[],
87+
"Write Property Principals",
88+
),
89+
]
90+
91+
for sid, rights in security.aces.items():
92+
rights = rights["rights"]
93+
sid = regcertipy.utils.sid_to_name(sid)
94+
95+
for right, principal_list, _ in rights_mapping:
96+
if right in rights:
97+
principal_list.append(sid)
98+
99+
for _, rights, name in rights_mapping:
100+
if len(rights) > 0:
101+
object_control_permissions[name] = rights
102+
103+
if len(object_control_permissions) > 0:
104+
permissions["Object Control Permissions"] = object_control_permissions
105+
106+
return permissions
107+
108+
def to_dict(self):
109+
return {
110+
"Name": self.name,
111+
"Friendly Name": self.display_name,
112+
"Template OID": self.oid,
113+
"Validity Period": self.validity_period,
114+
"Renewal Period": self.renewal_period,
115+
"Name Flags": self.name_flags,
116+
"Enrollment Flags": self.enrollment_flags,
117+
"Signatures Required": self.signatures_required,
118+
"Extended Key Usage": self.extended_key_usage,
119+
"Permissions": self.permissions,
120+
}

0 commit comments

Comments
 (0)