mirror of
https://github.com/stefanberger/swtpm.git
synced 2026-08-07 11:47:42 +00:00
samples: Rewrite of swtpm_localca in 'C' (addresses issue #437)
This is a 1:1 rewrite of swtpm_localca in 'C' as a final step to get rid of the python dependency. It addresses issue #437. Tested on: Cygwin, Fedora 33, Ubuntu Xenial & Bionic, FreeBSD, DragonFlyBSD, OpenBSD (i386), Mac, Debian (recent), Centos-8, CentOS (recent), Alpine (recent), OpenSUSE (recent), Fedora 28 PPC Big Endian Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
This commit is contained in:
parent
b1cde69b09
commit
e689684c57
3
.gitignore
vendored
3
.gitignore
vendored
@ -40,11 +40,10 @@ Makefile
|
||||
/include/swtpm.h
|
||||
/man/man3/*.3
|
||||
/man/man8/*.8
|
||||
/samples/dist
|
||||
/samples/py_swtpm_localca/swtpm_localca_conf.py
|
||||
/samples/swtpm-create-user-config-files
|
||||
/samples/swtpm-localca
|
||||
/samples/swtpm-localca.conf
|
||||
/samples/swtpm_localca_conf.h
|
||||
/src/selinux/*.pp.bz2
|
||||
/src/selinux/swtpm.pp
|
||||
/src/selinux/swtpm_svirt.fc
|
||||
|
||||
2
CHANGES
2
CHANGES
@ -3,6 +3,8 @@ CHANGES - changes for swtpm
|
||||
version 0.6.0
|
||||
- swtpm_setup:
|
||||
- Rewritten in 'C'; needs json-glib
|
||||
- swtpm-localca:
|
||||
- Rewritten in 'C'
|
||||
|
||||
version 0.5.0:
|
||||
- swtpm:
|
||||
|
||||
@ -27,7 +27,4 @@ selinux-install selinux-uninstall:
|
||||
@cd src/selinux && $(MAKE) $(AM_MAKEFLAGS) $@
|
||||
endif
|
||||
|
||||
python-install python-uninstall:
|
||||
@cd samples && $(MAKE) $(AM_MAKEFLAGS) $@
|
||||
@cd src/swtpm_setup && $(MAKE) $(AM_MAKEFLAGS) $@
|
||||
.PHONY: python-install python-uninstall selinux-install selinux-uninstall
|
||||
.PHONY: selinux-install selinux-uninstall
|
||||
|
||||
18
configure.ac
18
configure.ac
@ -382,16 +382,6 @@ if test "x$PIP3" = "x"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([for python setuptools package])
|
||||
$PYTHON -c "import setuptools"
|
||||
AS_IF([ test $? = 0 ],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[AC_MSG_ERROR([python setuptools is required])])
|
||||
|
||||
AC_ARG_ENABLE([python-installation],
|
||||
AS_HELP_STRING([--disable-python-installation], [Disable running setup.py install for swtpm_setup]))
|
||||
AM_CONDITIONAL([PYTHON_INSTALLATION], [test "x$enable_python_installation" != "xno"])
|
||||
|
||||
AC_ARG_ENABLE([hardening],
|
||||
AS_HELP_STRING([--disable-hardening], [Disable hardening flags]))
|
||||
|
||||
@ -560,9 +550,9 @@ AC_CONFIG_FILES([Makefile \
|
||||
etc/Makefile \
|
||||
etc/swtpm_setup.conf \
|
||||
samples/Makefile \
|
||||
samples/py_swtpm_localca/swtpm_localca_conf.py \
|
||||
samples/swtpm-localca.conf \
|
||||
samples/swtpm-create-user-config-files \
|
||||
samples/swtpm_localca_conf.h \
|
||||
include/Makefile \
|
||||
include/swtpm/Makefile \
|
||||
include/swtpm.h \
|
||||
@ -581,12 +571,6 @@ AC_CONFIG_FILES([Makefile \
|
||||
tests/Makefile \
|
||||
tests/test_config \
|
||||
])
|
||||
AC_CONFIG_FILES([samples/swtpm-localca],
|
||||
[chmod 755 samples/swtpm-localca])
|
||||
dnl for out-of-tree builds:
|
||||
AC_CONFIG_LINKS([samples/py_swtpm_localca/__init__.py:samples/py_swtpm_localca/__init__.py \
|
||||
samples/py_swtpm_localca/swtpm_localca.py:samples/py_swtpm_localca/swtpm_localca.py \
|
||||
samples/py_swtpm_localca/swtpm_utils.py:samples/py_swtpm_localca/swtpm_utils.py])
|
||||
AC_OUTPUT
|
||||
|
||||
echo
|
||||
|
||||
1
debian/control
vendored
1
debian/control
vendored
@ -14,7 +14,6 @@ Build-Depends: automake,
|
||||
expect,
|
||||
libtasn1-dev,
|
||||
socat,
|
||||
python3-setuptools,
|
||||
python3-twisted,
|
||||
gnutls-dev,
|
||||
gnutls-bin,
|
||||
|
||||
3
dist/swtpm.spec
vendored
3
dist/swtpm.spec
vendored
@ -28,9 +28,6 @@ BuildRequires: expect
|
||||
BuildRequires: net-tools
|
||||
BuildRequires: openssl-devel
|
||||
BuildRequires: socat
|
||||
BuildRequires: python3
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-setuptools
|
||||
BuildRequires: python3-twisted
|
||||
BuildRequires: softhsm
|
||||
BuildRequires: trousers >= 0.3.9
|
||||
|
||||
3
dist/swtpm.spec.in
vendored
3
dist/swtpm.spec.in
vendored
@ -28,9 +28,6 @@ BuildRequires: expect
|
||||
BuildRequires: net-tools
|
||||
BuildRequires: openssl-devel
|
||||
BuildRequires: socat
|
||||
BuildRequires: python3
|
||||
BuildRequires: python3-devel
|
||||
BuildRequires: python3-setuptools
|
||||
BuildRequires: python3-twisted
|
||||
BuildRequires: softhsm
|
||||
BuildRequires: trousers >= 0.3.9
|
||||
|
||||
@ -6,54 +6,44 @@
|
||||
|
||||
samplesconfdir = $(datadir)/@PACKAGE@
|
||||
samplessysconfdir = $(sysconfdir)
|
||||
# swtpm-localca was always installed in /usr/samples/swtpm-localca
|
||||
bindir = $(samplesconfdir)
|
||||
|
||||
samplesconf_SCRIPTS = \
|
||||
swtpm-create-tpmca \
|
||||
swtpm-create-user-config-files \
|
||||
swtpm-localca
|
||||
swtpm-create-user-config-files
|
||||
|
||||
samplessysconf_DATA = \
|
||||
swtpm-localca.conf \
|
||||
swtpm-localca.options
|
||||
|
||||
PY_SWTPM_LOCALCA_FILES = $(wildcard py_swtpm_localca/*.py)
|
||||
noinst_HEADERS = \
|
||||
swtpm_localca.h \
|
||||
swtpm_localca_utils.h
|
||||
|
||||
PY_PACKAGE_NAME=swtpm_localca
|
||||
PY_PACKAGE_NV=$(PY_PACKAGE_NAME)-$(SWTPM_VER_MAJOR).$(SWTPM_VER_MINOR).$(SWTPM_VER_MICRO)
|
||||
PY_PACKAGE=dist/$(PY_PACKAGE_NV).tar.gz
|
||||
bin_PROGRAMS = \
|
||||
swtpm-localca
|
||||
|
||||
# for out-of-tree builds we need to copy setup.py
|
||||
$(PY_PACKAGE): $(PY_SWTPM_LOCALCA_FILES)
|
||||
@$(CP) $(srcdir)/setup.py ./ &>/dev/null || true
|
||||
./setup.py sdist
|
||||
$(top_builddir)/src/utils/libswtpm_utils.la:
|
||||
$(MAKE) -C$(dir $@)
|
||||
|
||||
all-local: $(PY_PACKAGE)
|
||||
swtpm_localca_DEPENDENCIES = \
|
||||
$(top_builddir)/src/utils/libswtpm_utils.la
|
||||
|
||||
python-install: $(PY_PACKAGE)
|
||||
echo "Installing python to DESTDIR '/$(DESTDIR)'"
|
||||
./setup.py install --prefix=$(prefix) --root "/$(DESTDIR)" --install-layout deb 2>/dev/null || \
|
||||
./setup.py install --prefix=$(prefix) --root "/$(DESTDIR)" 2>/dev/null
|
||||
swtpm_localca_SOURCES = \
|
||||
swtpm_localca.c \
|
||||
swtpm_localca_utils.c
|
||||
|
||||
python-uninstall:
|
||||
echo "Local pip3 uninstall"
|
||||
$(PIP3) uninstall -y $(PY_PACKAGE_NAME)
|
||||
swtpm_localca_LDADD = \
|
||||
$(top_builddir)/src/utils/libswtpm_utils.la
|
||||
|
||||
if PYTHON_INSTALLATION
|
||||
install-exec-local: $(PY_PACKAGE)
|
||||
@if ! test $(findstring /usr, "$(DESTDIR)$(bindir)"); then \
|
||||
echo "Warning: Not installing python package to $(DESTDIR)$(bindir)"; \
|
||||
else \
|
||||
$(MAKE) python-install; \
|
||||
fi
|
||||
|
||||
uninstall-local:
|
||||
@if ! test $(findstring /usr, "$(DESTDIR)$(bindir)"); then \
|
||||
echo "Cleanup for distcheck build not implemented" ; \
|
||||
else \
|
||||
$(MAKE) python-uninstall; \
|
||||
fi
|
||||
endif
|
||||
swtpm_localca_LDFLAGS = \
|
||||
-L$(top_builddir)/src/utils -lswtpm_utils \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
swtpm_localca_CFLAGS = \
|
||||
-I$(top_srcdir)/src/utils \
|
||||
$(GLIB_CFLAGS)
|
||||
|
||||
install-data-local:
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/lib/swtpm-localca
|
||||
@ -62,19 +52,8 @@ install-data-local:
|
||||
chmod 0750 $(DESTDIR)$(localstatedir)/lib/swtpm-localca || true; \
|
||||
fi
|
||||
|
||||
# for out-of-tree builds we need to clean up
|
||||
clean-local:
|
||||
if test "$(top_srcdir)" != "$(top_builddir)"; then \
|
||||
rm -f setup.py; \
|
||||
fi
|
||||
rm -rf py_swtpm_localca/__pycache__/
|
||||
rm -rf swtpm_localca.egg-info/
|
||||
rm -rf dist
|
||||
|
||||
EXTRA_DIST= \
|
||||
setup.py \
|
||||
swtpm-create-tpmca \
|
||||
swtpm-create-user-config-files \
|
||||
swtpm-localca \
|
||||
swtpm-localca.conf \
|
||||
swtpm-localca.options
|
||||
|
||||
@ -1,701 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
""" swtpm_localca.py
|
||||
|
||||
A tool for creating TPM 1.2 and TPM 2 certificates localy or using pkcs11
|
||||
"""
|
||||
|
||||
# Disable a couple of warnings:
|
||||
# R0911: Too many return statements (10/6) (too-many-return-statements)
|
||||
# R0912: Too many branches (15/12) (too-many-branches)
|
||||
# R0913: Too many arguments (14/5) (too-many-arguments)
|
||||
# R0914: Too many local variables (21/15) (too-many-locals)
|
||||
# R0915: Too many statements (57/50) (too-many-statements)
|
||||
# W0703: Catching too general exception Exception (broad-except)
|
||||
# pylint: disable=W0703,R0911,R0912,R0913,R0914,R0915
|
||||
|
||||
#
|
||||
# swtpm_localca.py
|
||||
#
|
||||
# Authors: Stefan Berger <stefanb@linux.ibm.com>
|
||||
#
|
||||
# (c) Copyright IBM Corporation 2020
|
||||
#
|
||||
|
||||
import codecs
|
||||
import fcntl
|
||||
import getopt
|
||||
import getpass
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from py_swtpm_localca.swtpm_localca_conf import SYSCONFDIR
|
||||
from py_swtpm_localca.swtpm_utils import logit, logerr
|
||||
|
||||
# Some flags
|
||||
SETUP_TPM2_F = 1
|
||||
# for TPM 2 EK
|
||||
ALLOW_SIGNING_F = 2
|
||||
DECRYPTION_F = 4
|
||||
|
||||
|
||||
XCH = os.getenv("XDG_CONFIG_HOME")
|
||||
HOME = os.getenv("HOME")
|
||||
|
||||
LOCALCA_OPTIONS = "swtpm-localca.options"
|
||||
if XCH and os.access(os.path.join(XCH, LOCALCA_OPTIONS), os.R_OK):
|
||||
DEFAULT_LOCALCA_OPTIONS = os.path.join(XCH, LOCALCA_OPTIONS)
|
||||
elif HOME and os.access(os.path.join(HOME, ".config", LOCALCA_OPTIONS), os.R_OK):
|
||||
DEFAULT_LOCALCA_OPTIONS = os.path.join(HOME, ".config", LOCALCA_OPTIONS)
|
||||
else:
|
||||
DEFAULT_LOCALCA_OPTIONS = os.path.join(os.sep + SYSCONFDIR, LOCALCA_OPTIONS)
|
||||
|
||||
LOCALCA_CONFIG = "swtpm-localca.conf"
|
||||
if XCH and os.access(os.path.join(XCH, LOCALCA_CONFIG), os.R_OK):
|
||||
DEFAULT_LOCALCA_CONFIG = os.path.join(XCH, LOCALCA_CONFIG)
|
||||
elif HOME and os.access(os.path.join(HOME, ".config", LOCALCA_CONFIG), os.R_OK):
|
||||
DEFAULT_LOCALCA_CONFIG = os.path.join(HOME, ".config", LOCALCA_CONFIG)
|
||||
else:
|
||||
DEFAULT_LOCALCA_CONFIG = os.path.join(os.sep + SYSCONFDIR, LOCALCA_CONFIG)
|
||||
|
||||
# Default logging goes to stderr
|
||||
LOGFILE = ""
|
||||
|
||||
def resolve_string(inp):
|
||||
""" resolve environment variables in a string """
|
||||
result = ""
|
||||
sidx = 0
|
||||
|
||||
while True:
|
||||
idx = inp.find("${", sidx)
|
||||
if idx < 0:
|
||||
if sidx == 0:
|
||||
return inp
|
||||
result += inp[sidx:]
|
||||
return result
|
||||
|
||||
result += inp[sidx:idx]
|
||||
eidx = inp.find("}", idx + 2)
|
||||
if eidx < 0:
|
||||
result += inp[idx:]
|
||||
return result
|
||||
|
||||
result += os.getenv(inp[idx + 2:eidx], '')
|
||||
sidx = eidx + 1
|
||||
|
||||
|
||||
def get_config_value(lines, configname, default=None):
|
||||
""" Get a config value from a list of strings """
|
||||
regex = r'^' + configname + r"\s*=\s*([^#\n]*).*"
|
||||
for line in lines:
|
||||
match = re.match(regex, line)
|
||||
if match:
|
||||
return resolve_string(match.groups()[0])
|
||||
return default
|
||||
|
||||
|
||||
def get_config_envvars(lines):
|
||||
""" Extract all environment variables from the config file and return a map.
|
||||
Environment variable lines must start with 'env:' and must not contain
|
||||
trailing spaces or a comment starting with '#' """
|
||||
res = {}
|
||||
|
||||
regex = r"^env:([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*([^\n]*).*"
|
||||
for line in lines:
|
||||
match = re.match(regex, line)
|
||||
if match:
|
||||
try:
|
||||
encoded = codecs.encode(match.group(2), "latin-1", "backslashreplace")
|
||||
res[match.group(1)] = codecs.decode(encoded, "unicode_escape")
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Invalid character in value of %s environment variable: %s\n" %
|
||||
(match.group(1), str(err)))
|
||||
return {}, 1
|
||||
|
||||
return res, 0
|
||||
|
||||
|
||||
def write_file(filename, text):
|
||||
""" Write some text to a file """
|
||||
try:
|
||||
fileobj = open(filename, "w")
|
||||
fileobj.write(text)
|
||||
fileobj.close()
|
||||
return 0
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not write to file %s: %s\n" % (filename, str(err)))
|
||||
return 1
|
||||
|
||||
|
||||
def read_file(filename):
|
||||
""" read contents from a file """
|
||||
try:
|
||||
fobj = open(filename, mode='rb')
|
||||
result = fobj.read()
|
||||
fobj.close()
|
||||
return result, 0
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not read from file %s: %s\n" % (filename, str(err)))
|
||||
return "", 1
|
||||
|
||||
|
||||
def read_file_lines(filename):
|
||||
""" Read the lines from a file and return a list of the lines """
|
||||
try:
|
||||
fobj = open(filename, 'r')
|
||||
lines = fobj.readlines()
|
||||
fobj.close()
|
||||
return lines, 0
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not read from file %s : %s\n" & (filename, str(err)))
|
||||
return [], 1
|
||||
|
||||
|
||||
def makedir(dirname, purpose):
|
||||
""" Create a directory if it does not exist """
|
||||
if not os.path.exists(dirname):
|
||||
logit(LOGFILE, "Creating swtpm-local dir '%s'.\n" % dirname)
|
||||
try:
|
||||
os.makedirs(dirname)
|
||||
except OSError as err:
|
||||
logerr(LOGFILE, "Could not create directory for '%s': %s\n" % (purpose, str(err)))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def remove_file(filename, verbose=True):
|
||||
""" remove a file """
|
||||
if not os.path.exists(filename):
|
||||
return 0
|
||||
try:
|
||||
os.remove(filename)
|
||||
return 0
|
||||
except Exception as err:
|
||||
if verbose:
|
||||
logerr(LOGFILE, "Could not remove file %s: %s\n" % (filename, str(err)))
|
||||
return 1
|
||||
|
||||
|
||||
def remove_files(filename_list):
|
||||
""" remove files in a list of filenames """
|
||||
for filename in filename_list:
|
||||
remove_file(filename, verbose=False)
|
||||
|
||||
|
||||
def get_certtool():
|
||||
""" Get the name of the certtool to use """
|
||||
if os.uname().sysname == "Darwin":
|
||||
return "gnutls-certtool"
|
||||
return "certtool"
|
||||
|
||||
|
||||
def create_localca_cert(lockfile, statedir, signkey, signkey_password, issuercert):
|
||||
""" Create the local CA's certificate if it doesn't already exist. """
|
||||
try:
|
||||
filedes = os.open(lockfile, os.O_RDWR|os.O_CREAT)
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not open lockfile %s: %s\n" % (lockfile, str(err)))
|
||||
return 1
|
||||
|
||||
try:
|
||||
fcntl.flock(filedes, fcntl.LOCK_EX)
|
||||
|
||||
if not os.path.exists(statedir):
|
||||
if makedir(statedir, "statedir") != 0:
|
||||
return 1
|
||||
if not os.access(signkey, os.R_OK):
|
||||
directory = os.path.dirname(signkey)
|
||||
cakey = os.path.join(directory, "swtpm-localca-rootca-privkey.pem")
|
||||
cacert = os.path.join(directory, "swtpm-localca-rootca-cert.pem")
|
||||
|
||||
swtpm_rootca_password = os.getenv("SWTPM_ROOTCA_PASSWORD")
|
||||
certtool = get_certtool()
|
||||
|
||||
# First the root CA
|
||||
cmd = [certtool, "--generate-privkey", "--outfile", cakey]
|
||||
if swtpm_rootca_password:
|
||||
# neither env. variable nor template file work...
|
||||
cmd.extend(["--password", swtpm_rootca_password])
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = proc.communicate()[0]
|
||||
if proc.returncode:
|
||||
logerr(LOGFILE, "Could not create root-CA key %s\n" % cakey)
|
||||
logerr(LOGFILE, "%s" % output.decode())
|
||||
return 1
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not create root-CA key %s: %s\n" % (cakey, str(err)))
|
||||
return 1
|
||||
|
||||
os.chmod(cakey, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP)
|
||||
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
try:
|
||||
filecontent = \
|
||||
"cn=swtpm-localca-rootca\n" \
|
||||
"ca\n" \
|
||||
"cert_signing_key\n" \
|
||||
"expiration_days = 3650\n"
|
||||
temp.write(filecontent.encode())
|
||||
temp.seek(0)
|
||||
cmd = [certtool,
|
||||
"--generate-self-signed",
|
||||
"--template", temp.name,
|
||||
"--outfile", cacert,
|
||||
"--load-privkey", cakey]
|
||||
|
||||
certtool_env = {
|
||||
"PATH": os.getenv("PATH")
|
||||
}
|
||||
if swtpm_rootca_password:
|
||||
certtool_env["GNUTLS_PIN"] = swtpm_rootca_password
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, env=certtool_env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = proc.communicate()[0]
|
||||
if proc.returncode:
|
||||
logerr(LOGFILE, "Could not create root-CA\n")
|
||||
logerr(LOGFILE, "%s" % output.decode())
|
||||
remove_files([cakey, cacert])
|
||||
return 1
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not create root-CA: %s\n" % str(err))
|
||||
remove_files([cakey, cacert])
|
||||
return 1
|
||||
finally:
|
||||
temp.close()
|
||||
|
||||
# intermediate CA
|
||||
cmd = [certtool, "--generate-privkey", "--outfile", signkey]
|
||||
if signkey_password:
|
||||
cmd.extend(["--password", signkey_password])
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = proc.communicate()[0]
|
||||
if proc.returncode:
|
||||
logerr(LOGFILE, "Could not create local-CA key %s\n" % signkey)
|
||||
logerr(LOGFILE, "certtool failed: %s\n" % output.decode())
|
||||
remove_files([cakey, cacert, signkey])
|
||||
return 1
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not create local-CA key %s: %s\n" % (signkey, str(err)))
|
||||
remove_files([cakey, cacert, signkey])
|
||||
return 1
|
||||
|
||||
os.chmod(signkey, stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP)
|
||||
|
||||
temp = tempfile.NamedTemporaryFile()
|
||||
try:
|
||||
filecontent = \
|
||||
"cn=swtpm-localca\n" \
|
||||
"ca\n" \
|
||||
"cert_signing_key\n" \
|
||||
"expiration_days = 3650\n"
|
||||
if swtpm_rootca_password and signkey_password:
|
||||
filecontent += "password = %s\n" % swtpm_rootca_password
|
||||
temp.write(filecontent.encode())
|
||||
temp.seek(0)
|
||||
|
||||
cmd = [certtool,
|
||||
"--generate-certificate",
|
||||
"--template", temp.name,
|
||||
"--outfile", issuercert,
|
||||
"--load-privkey", signkey,
|
||||
"--load-ca-privkey", cakey,
|
||||
"--load-ca-certificate", cacert]
|
||||
|
||||
certtool_env = {
|
||||
"PATH": os.getenv("PATH")
|
||||
}
|
||||
if signkey_password:
|
||||
certtool_env["GNUTLS_PIN"] = signkey_password
|
||||
elif swtpm_rootca_password:
|
||||
certtool_env["GNUTLS_PIN"] = swtpm_rootca_password
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, env=certtool_env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = proc.communicate()[0]
|
||||
if proc.returncode:
|
||||
logerr(LOGFILE, "Could not create local CA\n")
|
||||
logerr(LOGFILE, "%s" % output.decode())
|
||||
remove_files([cakey, cacert, signkey, issuercert])
|
||||
return 1
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not create local CA: %s\n" % str(err))
|
||||
remove_files([cakey, cacert, signkey, issuercert])
|
||||
return 1
|
||||
finally:
|
||||
temp.close()
|
||||
finally:
|
||||
os.close(filedes)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def get_next_cert_serial(certserial, lockfile):
|
||||
""" Get the next serial number for a certificate """
|
||||
try:
|
||||
filedes = os.open(lockfile, os.O_RDWR|os.O_CREAT)
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not open lockfile %s: %s\n" % (lockfile, str(err)))
|
||||
return 1
|
||||
|
||||
try:
|
||||
fcntl.flock(filedes, fcntl.LOCK_EX)
|
||||
|
||||
if not os.access(certserial, os.R_OK):
|
||||
_ = write_file(certserial, "1")
|
||||
serial, ret = read_file(certserial)
|
||||
if ret != 0:
|
||||
return "", 1
|
||||
if not serial.decode().isnumeric():
|
||||
serial_n = 1
|
||||
else:
|
||||
serial_n = int(serial) + 1
|
||||
ret = write_file(certserial, "%d" % serial_n)
|
||||
if ret != 0:
|
||||
return "", 1
|
||||
finally:
|
||||
os.close(filedes)
|
||||
|
||||
return "%d" % serial_n, 0
|
||||
|
||||
|
||||
def create_cert(flags, typ, directory, ekparams, vmid, tpm_spec_params, tpm_attr_params,
|
||||
signkey, signkey_password, issuercert, parentkey_password, swtpm_cert_env,
|
||||
certserial, lockfile, optsfile):
|
||||
""" Create the certificate """
|
||||
serial, ret = get_next_cert_serial(certserial, lockfile)
|
||||
if ret != 0:
|
||||
return 1
|
||||
|
||||
options = []
|
||||
lines, _ = read_file_lines(optsfile)
|
||||
for line in lines:
|
||||
if not line.strip():
|
||||
continue
|
||||
options.extend([x.strip() for x in line.split(" ", 1)])
|
||||
|
||||
if vmid:
|
||||
subj = "CN=%s" % vmid
|
||||
else:
|
||||
subj = "CN=unknown"
|
||||
|
||||
if flags & SETUP_TPM2_F:
|
||||
options.append("--tpm2")
|
||||
else:
|
||||
options.append("--add-header")
|
||||
|
||||
if typ == "ek":
|
||||
if flags & ALLOW_SIGNING_F:
|
||||
options.append("--allow-signing")
|
||||
if flags & DECRYPTION_F:
|
||||
options.append("--decryption")
|
||||
|
||||
match = re.search(r'x=([0-9A-Fa-f]+),y=([0-9A-Fa-f]+)(,id=([^,]+))?', ekparams)
|
||||
if match:
|
||||
keyparams = ["--ecc-x", match.group(1), "--ecc-y", match.group(2)]
|
||||
if match.group(4):
|
||||
keyparams.extend(["--ecc-curveid", match.group(4)])
|
||||
else:
|
||||
keyparams = ["--modulus", ekparams]
|
||||
|
||||
cmd = ["swtpm_cert",
|
||||
"--subject", subj]
|
||||
cmd.extend(options)
|
||||
|
||||
temp1 = None
|
||||
temp2 = None
|
||||
|
||||
if signkey_password:
|
||||
temp1 = tempfile.NamedTemporaryFile()
|
||||
temp1.write(signkey_password.encode())
|
||||
temp1.seek(0)
|
||||
cmd.extend(["--signkey-pwd", "file:%s" % temp1.name])
|
||||
|
||||
if parentkey_password:
|
||||
temp2 = tempfile.NamedTemporaryFile()
|
||||
temp2.write(parentkey_password.encode())
|
||||
temp2.seek(0)
|
||||
cmd.extend(["--parentkey-pwd", "file:%s" % temp2.name])
|
||||
|
||||
if typ == "ek":
|
||||
cmd.extend(tpm_spec_params)
|
||||
|
||||
cmd.extend(tpm_attr_params)
|
||||
|
||||
if typ == "platform":
|
||||
cmd.extend(["--type", "platform",
|
||||
"--out-cert", os.path.join(directory, "platform.cert")])
|
||||
else:
|
||||
cmd.extend(["--out-cert", os.path.join(directory, "ek.cert")])
|
||||
|
||||
cmd.extend(keyparams)
|
||||
cmd.extend(["--signkey", signkey,
|
||||
"--issuercert", issuercert,
|
||||
"--days", "3650",
|
||||
"--serial", serial])
|
||||
|
||||
if typ == "ek":
|
||||
certtype = "EK"
|
||||
else:
|
||||
certtype = "platform"
|
||||
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, env=swtpm_cert_env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = proc.communicate()[0]
|
||||
if proc.returncode:
|
||||
logerr(LOGFILE, "Could not create %s certificate locally\n" % certtype)
|
||||
logerr(LOGFILE, "%s" % output.decode())
|
||||
return 1
|
||||
except Exception as err:
|
||||
logerr(LOGFILE, "Could not run swtpm_cert: %s\n" % str(err))
|
||||
return 1
|
||||
finally:
|
||||
if temp1:
|
||||
temp1.close()
|
||||
if temp2:
|
||||
temp2.close()
|
||||
|
||||
logit(LOGFILE, "Successfully created %s certificate locally.\n" % certtype)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def usage(prgname):
|
||||
""" Display usage """
|
||||
print(
|
||||
"Usage: {prgname} [options]\n"
|
||||
"\n"
|
||||
"The following options are supported:\n"
|
||||
"\n"
|
||||
"--type type The type of certificate to create: 'ek' or 'platform'\n"
|
||||
"--ek key-param The modulus of an RSA key or x=...,y=,... for an EC key\n"
|
||||
"--dir directory The directory to write the resulting certificate into\n"
|
||||
"--vmid vmid The ID of the virtual machine\n"
|
||||
"--optsfile file A file containing options to pass to swtpm_cert\n"
|
||||
"--configfile file A file containing configuration parameters for directory,\n"
|
||||
" signing key and password and certificate to use\n"
|
||||
"--logfile file A file to write a log into\n"
|
||||
"--tpm-spec-family s The implemented spec family, e.g., '2.0'\n"
|
||||
"--tpm-spec-revision i The spec revision of the TPM as integer; e.g., 146\n"
|
||||
"--tpm-spec-level i The spec level of the TPM; must be an integer; e.g. 0\n"
|
||||
"--tpm-manufacturer s The manufacturer of the TPM; e.g., id:00001014\n"
|
||||
"--tpm-model s The model of the TPM; e.g., 'swtpm'\n"
|
||||
"--tpm-version i The (firmware) version of the TPM; e.g., id:20160511\n"
|
||||
"--tpm2 Generate a certificate for a TPM 2\n"
|
||||
"--allow-signing The TPM 2's EK can be used for signing\n"
|
||||
"--decryption The TPM 2's EK can be used for decryption\n"
|
||||
"--help, -h, -? Display this help screen and exit\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"The following environment variables are supported:\n"
|
||||
"\n"
|
||||
"SWTPM_ROOTCA_PASSWORD The root CA's private key password\n"
|
||||
"\n".format_map({
|
||||
'prgname': prgname,
|
||||
}))
|
||||
|
||||
|
||||
def main():
|
||||
""" main function - parses command line parameters and low level dealing with them """
|
||||
global LOGFILE # pylint: disable=W0603
|
||||
|
||||
try:
|
||||
opts, _ = getopt.getopt(sys.argv[1:], "h?",
|
||||
["type=",
|
||||
"ek=",
|
||||
"dir=",
|
||||
"vmid=",
|
||||
"optsfile=",
|
||||
"configfile=",
|
||||
"logfile=",
|
||||
"tpm-spec-family=",
|
||||
"tpm-spec-revision=",
|
||||
"tpm-spec-level=",
|
||||
"tpm-manufacturer=",
|
||||
"tpm-model=",
|
||||
"tpm-version=",
|
||||
"tpm2",
|
||||
"allow-signing",
|
||||
"decryption",
|
||||
"help"])
|
||||
except getopt.GetoptError as err:
|
||||
print(err)
|
||||
usage(sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
flags = 0
|
||||
typ = ""
|
||||
ekparams = ""
|
||||
directory = "." # default to current directory
|
||||
vmid = ""
|
||||
optsfile = DEFAULT_LOCALCA_OPTIONS
|
||||
configfile = DEFAULT_LOCALCA_CONFIG
|
||||
tpm_spec_params = []
|
||||
tpm_attr_params = []
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '--type':
|
||||
typ = arg
|
||||
elif opt == '--ek':
|
||||
ekparams = arg
|
||||
elif opt == '--dir':
|
||||
directory = arg
|
||||
elif opt == '--vmid':
|
||||
vmid = arg
|
||||
elif opt == '--optsfile':
|
||||
optsfile = arg
|
||||
elif opt == '--configfile':
|
||||
configfile = arg
|
||||
elif opt == '--logfile':
|
||||
LOGFILE = arg
|
||||
elif opt in ['--tpm-spec-family', '--tpm-spec-revision', '--tpm-spec-level']:
|
||||
tpm_spec_params.extend([opt, arg])
|
||||
elif opt in ['--tpm-manufacturer', '--tpm-model', '--tpm-version']:
|
||||
tpm_attr_params.extend([opt, arg])
|
||||
elif opt == '--tpm2':
|
||||
flags |= SETUP_TPM2_F
|
||||
elif opt == '--allow-signing':
|
||||
flags |= ALLOW_SIGNING_F
|
||||
elif opt == '--decryption':
|
||||
flags |= DECRYPTION_F
|
||||
elif opt in ['--help', '-h', '-?']:
|
||||
usage(sys.argv[0])
|
||||
sys.exit(0)
|
||||
|
||||
if len(LOGFILE) > 0:
|
||||
if os.path.islink(LOGFILE):
|
||||
sys.stderr.write("Logfile must not be a symlink.\n")
|
||||
sys.exit(1)
|
||||
try:
|
||||
fobj = open(LOGFILE, "a") # do not truncate
|
||||
fobj.close()
|
||||
except PermissionError:
|
||||
sys.stderr.write("Cannot write to logfile %s.\n", LOGFILE)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(optsfile, os.R_OK):
|
||||
logerr(LOGFILE, "Need read rights on options file %s for user %s.\n" %
|
||||
(optsfile, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(configfile, os.R_OK):
|
||||
logerr(LOGFILE, "Need read rights on options file %s for user %s.\n" %
|
||||
(configfile, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
lines, ret = read_file_lines(configfile)
|
||||
if ret != 0:
|
||||
sys.exit(1)
|
||||
|
||||
statedir = get_config_value(lines, "statedir")
|
||||
if not statedir:
|
||||
logerr(LOGFILE, "Missing 'statedir' config value in config file %s.\n" % configfile)
|
||||
sys.exit(1)
|
||||
if makedir(statedir, "statedir") != 0:
|
||||
sys.exit(1)
|
||||
if not os.access(statedir, os.W_OK | os.R_OK):
|
||||
logerr(LOGFILE, "Need read/write rights on statedir %s for user %s.\n" %
|
||||
(statedir, getpass.getuser()))
|
||||
|
||||
lockfile = os.path.join(statedir, ".lock.swtpm-localca")
|
||||
if os.path.exists(lockfile) and not os.access(lockfile, os.W_OK | os.R_OK):
|
||||
logerr(LOGFILE, "Need read/write rights on %s for user %s.\n" %
|
||||
(lockfile, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
signkey = get_config_value(lines, "signingkey")
|
||||
if not signkey:
|
||||
logerr(LOGFILE, "Missing 'signingkey' config value in config file %s.\n" % configfile)
|
||||
sys.exit(1)
|
||||
|
||||
# SIGNKEY may be a GNUTLS url like tpmkey:file= or tpmkey:uuid=
|
||||
if not signkey.startswith("tpmkey:file=") and \
|
||||
not signkey.startswith("tpmkey:uuid=") and \
|
||||
not signkey.startswith("pkcs11:"):
|
||||
if makedir(os.path.dirname(signkey), "signkey") != 0:
|
||||
sys.exit(1)
|
||||
|
||||
signkey_password = get_config_value(lines, "signingkey_password")
|
||||
parentkey_password = get_config_value(lines, "parentkey_password")
|
||||
|
||||
issuercert = get_config_value(lines, 'issuercert')
|
||||
if not issuercert:
|
||||
logerr(LOGFILE, "Missing 'issuercert' config value in config file %s.\n" % configfile)
|
||||
sys.exit(1)
|
||||
if makedir(os.path.dirname(issuercert), "issuercert") != 0:
|
||||
sys.exit(1)
|
||||
|
||||
# environment needed for calling swtpm_cert
|
||||
swtpm_cert_env = os.environ
|
||||
|
||||
# TPM keys are GNUTLS URIs...
|
||||
if signkey.startswith("tpmkey:file=") or signkey.startswith("tpmkey:uuid="):
|
||||
tss_tcsd_hostname = get_config_value(lines, "TSS_TCSD_HOSTNAME", "localhost")
|
||||
tss_tcsd_port = get_config_value(lines, "TSS_TCSD_PORT", 30003)
|
||||
swtpm_cert_env["TSS_TCSD_HOSTNAME"] = tss_tcsd_hostname
|
||||
swtpm_cert_env["TSS_TCSD_PORT"] = tss_tcsd_port
|
||||
|
||||
logit(LOGFILE, "CA uses a GnuTLS TPM key; using TSS_TCSD_HOSTNAME=%s " \
|
||||
"TSS_TCSD_PORT=%s\n" % (tss_tcsd_hostname, tss_tcsd_port))
|
||||
elif signkey.startswith("pkcs11:"):
|
||||
signkey = signkey.replace(r"\;", ";")
|
||||
if signkey_password:
|
||||
swtpm_cert_env["SWTPM_PKCS11_PIN"] = signkey_password
|
||||
logit(LOGFILE, "CA uses a PKCS#11 key; using password from 'signingkey_password'\n")
|
||||
else:
|
||||
swtpm_pkcs11_pin = get_config_value(lines, "SWTPM_PKCS11_PIN", "swtpm-tpmca")
|
||||
swtpm_cert_env["SWTPM_PKCS11_PIN"] = swtpm_pkcs11_pin
|
||||
logit(LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCS11_PIN\n")
|
||||
# Get additional environment variables pkcs11 modules may need
|
||||
envvars, ret = get_config_envvars(lines)
|
||||
if ret != 0:
|
||||
sys.exit(1)
|
||||
swtpm_cert_env.update(envvars)
|
||||
else:
|
||||
# if signkey does not exists it will be created...
|
||||
if not os.access(signkey, os.R_OK):
|
||||
if os.path.exists(signkey):
|
||||
logerr(LOGFILE, "Need read rights on signing key %s for user %s.\n" %
|
||||
(signkey, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
logit(LOGFILE, "Creating root CA and a local CA's signing key and issuer cert.\n")
|
||||
if create_localca_cert(lockfile, statedir, signkey, signkey_password,
|
||||
issuercert) != 0:
|
||||
logerr(LOGFILE, "Error creating local CA's signing key and cert.\n")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(signkey, os.R_OK):
|
||||
logerr(LOGFILE, "Need read rights on signing key %s for user %s.\n" %
|
||||
(signkey, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
if not os.access(issuercert, os.R_OK):
|
||||
logerr(LOGFILE, "Need read rights on issuer certificate %s for user %s.\n" %
|
||||
(issuercert, getpass.getuser()))
|
||||
sys.exit(1)
|
||||
|
||||
certserial = get_config_value(lines, "certserial", os.path.join(statedir, "certserial"))
|
||||
if makedir(os.path.dirname(certserial), "certserial") != 0:
|
||||
sys.exit(1)
|
||||
|
||||
ret = create_cert(flags, typ, directory, ekparams, vmid, tpm_spec_params, tpm_attr_params,
|
||||
signkey, signkey_password, issuercert, parentkey_password, swtpm_cert_env,
|
||||
certserial, lockfile, optsfile)
|
||||
|
||||
sys.exit(ret)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,11 +0,0 @@
|
||||
|
||||
""" swtpm_localca_conf.py
|
||||
|
||||
This module holds compile-time constants
|
||||
"""
|
||||
|
||||
SWTPM_VER_MAJOR = @SWTPM_VER_MAJOR@
|
||||
SWTPM_VER_MINOR = @SWTPM_VER_MINOR@
|
||||
SWTPM_VER_MICRO = @SWTPM_VER_MICRO@
|
||||
|
||||
SYSCONFDIR = "@SYSCONFDIR@"
|
||||
@ -1,40 +0,0 @@
|
||||
""" swtpm_logging.py
|
||||
"""
|
||||
|
||||
# pylint: disable=W0703
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def append_to_file(filename, string):
|
||||
"""" Append a string to a file """
|
||||
try:
|
||||
filedesc = os.open(filename, os.O_WRONLY|os.O_APPEND|os.O_CREAT|os.O_NOFOLLOW, 0o640)
|
||||
os.write(filedesc, string.encode('utf-8'))
|
||||
os.close(filedesc)
|
||||
except Exception as ex:
|
||||
sys.stdout.write("Error: %s\n" % ex)
|
||||
sys.stdout.write(string)
|
||||
try:
|
||||
if filedesc > 0:
|
||||
os.close(filedesc)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def logit(logfile, string):
|
||||
""" Print the given string to stdout or into the logfile """
|
||||
if len(logfile) == 0:
|
||||
sys.stdout.write(string)
|
||||
else:
|
||||
append_to_file(logfile, string)
|
||||
|
||||
|
||||
def logerr(logfile, string):
|
||||
""" Print the given string to stderr or into the logfile """
|
||||
if len(logfile) == 0:
|
||||
sys.stdout.write(string)
|
||||
else:
|
||||
append_to_file(logfile, string)
|
||||
@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
""" setup.py
|
||||
|
||||
Install swtpm-localca
|
||||
"""
|
||||
import setuptools
|
||||
|
||||
from py_swtpm_localca.swtpm_localca_conf import SWTPM_VER_MAJOR, SWTPM_VER_MINOR, SWTPM_VER_MICRO
|
||||
|
||||
setuptools.setup(
|
||||
name="swtpm-localca",
|
||||
version="%d.%d.%d" % (SWTPM_VER_MAJOR, SWTPM_VER_MINOR, SWTPM_VER_MICRO),
|
||||
author="Stefan Berger et al.",
|
||||
author_email="stefanb@linux.ibm.com",
|
||||
url="https::/github.com/stefanberger/swtpm",
|
||||
#packages=setuptools.find_packages(),
|
||||
description="A local CA for creating TPM 1.2 and TPM 2 EK and platform certificates",
|
||||
python_requires=">=3.2",
|
||||
packages=["py_swtpm_localca"],
|
||||
package_dir={
|
||||
"py_swtpm_localca": "py_swtpm_localca"
|
||||
},
|
||||
license="BSD3",
|
||||
install_requires=[
|
||||
],
|
||||
)
|
||||
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Launcher for swtpm-localca
|
||||
"""
|
||||
|
||||
from py_swtpm_localca.swtpm_localca import main
|
||||
|
||||
main()
|
||||
864
samples/swtpm_localca.c
Normal file
864
samples/swtpm_localca.c
Normal file
@ -0,0 +1,864 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* swtpm_localca.c: A tool for creating TPM 1.2 and TPM 2 certificates localy or using pkcs11
|
||||
*
|
||||
* Author: Stefan Berger, stefanb@linux.ibm.com
|
||||
*
|
||||
* Copyright (c) IBM Corporation, 2021
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <regex.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "swtpm_utils.h"
|
||||
#include "swtpm_localca_conf.h"
|
||||
#include "swtpm_localca_utils.h"
|
||||
|
||||
#define SETUP_TPM2_F 1
|
||||
/* for TPM 2 EK */
|
||||
#define ALLOW_SIGNING_F 2
|
||||
#define DECRYPTION_F 4
|
||||
|
||||
/* Default logging goes to stderr */
|
||||
gchar *gl_LOGFILE = NULL;
|
||||
|
||||
#define LOCALCA_OPTIONS "swtpm-localca.options"
|
||||
#define LOCALCA_CONFIG "swtpm-localca.conf"
|
||||
|
||||
#if defined __APPLE__
|
||||
# define CERTTOOL_NAME "gnutls-certtool"
|
||||
#else
|
||||
# define CERTTOOL_NAME "certtool"
|
||||
#endif
|
||||
|
||||
/* initialize the path of the options and config files */
|
||||
static int init(gchar **options_file, gchar **config_file)
|
||||
{
|
||||
const char *xch = getenv("XDG_CONFIG_HOME");
|
||||
const char *home = getenv("HOME");
|
||||
char path[PATH_MAX];
|
||||
const char *p = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (xch != NULL &&
|
||||
(p = pathjoin(path, sizeof(path), xch, LOCALCA_OPTIONS, NULL)) != NULL &&
|
||||
access(p, R_OK) == 0) {
|
||||
/* p is good */
|
||||
} else if (home != NULL &&
|
||||
(p = pathjoin(path, sizeof(path), home, ".config", LOCALCA_OPTIONS)) != NULL &&
|
||||
access(p, R_OK) == 0) {
|
||||
/* p is good */
|
||||
} else {
|
||||
p = pathjoin(path, sizeof(path), G_DIR_SEPARATOR_S, SYSCONFDIR, LOCALCA_OPTIONS);
|
||||
}
|
||||
*options_file = g_strdup(p);
|
||||
|
||||
if (xch != NULL &&
|
||||
(p = pathjoin(path, sizeof(path), xch, LOCALCA_CONFIG, NULL)) != NULL &&
|
||||
access(p, R_OK) == 0) {
|
||||
/* p is good */
|
||||
} else if (home != NULL &&
|
||||
(p = pathjoin(path, sizeof(path), home, ".config", LOCALCA_CONFIG)) != NULL &&
|
||||
access(p, R_OK) == 0) {
|
||||
/* p is good */
|
||||
} else {
|
||||
p = pathjoin(path, sizeof(path), G_DIR_SEPARATOR_S, SYSCONFDIR, LOCALCA_CONFIG);
|
||||
}
|
||||
*config_file = g_strdup(p);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Run the certtool command line prepared in cmd. Display error message
|
||||
* in case of failure and also display the keyfile if something goes wrong.
|
||||
*/
|
||||
static int run_certtool(gchar **cmd, gchar **env, const char *msg, gchar *keyfile)
|
||||
{
|
||||
g_autofree gchar *standard_error = NULL;
|
||||
gint exit_status;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
||||
success = g_spawn_sync(NULL, cmd, env, G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL,
|
||||
NULL, &standard_error, &exit_status, &error);
|
||||
if (!success || exit_status != 0) {
|
||||
logerr(gl_LOGFILE, "%s" , msg);
|
||||
if (keyfile)
|
||||
logerr(gl_LOGFILE, " %s:", keyfile);
|
||||
if (!success) {
|
||||
logerr(gl_LOGFILE, "%s\n", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
logerr(gl_LOGFILE, "%s\n", standard_error);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create a root CA key and cert and a local CA key and cert. The latter will be
|
||||
* used for signing the TPM certs.
|
||||
*/
|
||||
static int create_localca_cert(const gchar *lockfile, const gchar *statedir,
|
||||
const gchar *signkey, const gchar *signkey_password,
|
||||
const gchar *issuercert)
|
||||
{
|
||||
int lockfd;
|
||||
int ret = 1;
|
||||
struct stat statbuf;
|
||||
int template1_file_fd = -1;
|
||||
int template2_file_fd = -1;
|
||||
g_autofree gchar *template1_file = NULL;
|
||||
g_autofree gchar *template2_file = NULL;
|
||||
gchar **certtool_env = NULL;
|
||||
|
||||
lockfd = lock_file(lockfile);
|
||||
if (lockfd < 0)
|
||||
return 1;
|
||||
|
||||
if (stat(statedir, &statbuf) != 0) {
|
||||
if (makedir(statedir, "statedir") != 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (access(signkey, R_OK) != 0) {
|
||||
g_autofree gchar *directory = g_path_get_dirname(signkey);
|
||||
g_autofree gchar *cakey = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-privkey.pem", NULL);
|
||||
g_autofree gchar *cacert = g_strjoin(G_DIR_SEPARATOR_S, directory, "swtpm-localca-rootca-cert.pem", NULL);
|
||||
const gchar *swtpm_rootca_password = g_getenv("SWTPM_ROOTCA_PASSWORD");
|
||||
g_autofree gchar *certtool = g_find_program_in_path(CERTTOOL_NAME);
|
||||
g_autofree gchar **cmd = NULL;
|
||||
g_autofree gchar *fc = NULL;
|
||||
const char *filecontent;
|
||||
|
||||
if (certtool == NULL) {
|
||||
logerr(gl_LOGFILE, "Could not find %s in PATH.\n", CERTTOOL_NAME);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* generate the root-CA's private key */
|
||||
cmd = concat_arrays(cmd, (gchar*[]){
|
||||
(gchar *)certtool, "--generate-privkey", "--outfile", cakey, NULL
|
||||
}, TRUE);
|
||||
if (swtpm_rootca_password != NULL)
|
||||
cmd = concat_arrays(cmd, (gchar*[]){
|
||||
"--password", (gchar *)swtpm_rootca_password, NULL
|
||||
}, TRUE);
|
||||
if (run_certtool(cmd, certtool_env, "Could not create root-CA key", cakey))
|
||||
goto error;
|
||||
|
||||
if (chmod(cakey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
|
||||
logerr(gl_LOGFILE, "Could not chmod %s: %s\n", cakey, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
certtool_env = g_environ_setenv(NULL, "PATH", g_getenv("PATH"), TRUE);
|
||||
|
||||
/* create the root-CA's cert */
|
||||
filecontent = "cn=swtpm-localca-rootca\n"
|
||||
"ca\n"
|
||||
"cert_signing_key\n"
|
||||
"expiration_days = 7200\n";
|
||||
template1_file_fd = write_to_tempfile(&template1_file,
|
||||
(const unsigned char *)filecontent, strlen(filecontent));
|
||||
if (template1_file_fd < 0)
|
||||
goto error;
|
||||
|
||||
g_free(cmd);
|
||||
cmd = concat_arrays(NULL,
|
||||
(gchar *[]) {
|
||||
certtool,
|
||||
"--generate-self-signed",
|
||||
"--template", template1_file,
|
||||
"--outfile", cacert,
|
||||
"--load-privkey", cakey,
|
||||
NULL
|
||||
}, FALSE);
|
||||
if (swtpm_rootca_password != NULL)
|
||||
certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE);
|
||||
|
||||
if (run_certtool(cmd, certtool_env, "Could not create root-CA:", NULL))
|
||||
goto error;
|
||||
|
||||
g_free(cmd);
|
||||
|
||||
/* create the intermediate CA's key */
|
||||
cmd = concat_arrays(NULL,
|
||||
(gchar *[]) {
|
||||
certtool, "--generate-privkey", "--outfile", (gchar *)signkey, NULL
|
||||
}, FALSE);
|
||||
if (signkey_password != NULL)
|
||||
cmd = concat_arrays(cmd, (gchar *[]){
|
||||
"--password", (gchar *)signkey_password, NULL},
|
||||
TRUE);
|
||||
if (run_certtool(cmd, certtool_env, "Could not create local-CA key", cakey))
|
||||
goto error;
|
||||
|
||||
if (chmod(signkey, S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
|
||||
logerr(gl_LOGFILE, "Could not chmod %s: %s\n", signkey, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
filecontent = "cn=swtpm-localca\n"
|
||||
"ca\n"
|
||||
"cert_signing_key\n"
|
||||
"expiration_days = 7200\n";
|
||||
if (swtpm_rootca_password != NULL && signkey_password != NULL)
|
||||
fc = g_strdup_printf("%spassword = %s\n", filecontent, swtpm_rootca_password);
|
||||
else
|
||||
fc = g_strdup(filecontent);
|
||||
|
||||
template2_file_fd = write_to_tempfile(&template2_file,
|
||||
(const unsigned char *)fc, strlen(fc));
|
||||
if (template2_file_fd < 0)
|
||||
goto error;
|
||||
|
||||
g_free(cmd);
|
||||
cmd = concat_arrays(NULL,
|
||||
(gchar *[]) {
|
||||
certtool,
|
||||
"--generate-certificate",
|
||||
"--template", template2_file,
|
||||
"--outfile", (gchar *)issuercert,
|
||||
"--load-privkey", (gchar *)signkey,
|
||||
"--load-ca-privkey", cakey,
|
||||
"--load-ca-certificate", cacert,
|
||||
NULL
|
||||
}, FALSE);
|
||||
if (signkey_password != NULL)
|
||||
certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", signkey_password, TRUE);
|
||||
else if (swtpm_rootca_password != NULL)
|
||||
certtool_env = g_environ_setenv(certtool_env, "GNUTLS_PIN", swtpm_rootca_password, TRUE);
|
||||
|
||||
if (run_certtool(cmd, certtool_env, "Could not create local-CA:", NULL))
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
error:
|
||||
if (template1_file_fd >= 0)
|
||||
close(template1_file_fd);
|
||||
if (template1_file != NULL)
|
||||
unlink(template1_file);
|
||||
|
||||
if (template2_file_fd >= 0)
|
||||
close(template2_file_fd);
|
||||
if (template2_file != NULL)
|
||||
unlink(template2_file);
|
||||
g_strfreev(certtool_env);
|
||||
|
||||
unlock_file(lockfd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Extract the ECC parameters from a string like x=12,y=34,id=secp384r1.
|
||||
* This function returns 1 on error, 2 if the ECC parameters could be extracted
|
||||
* and 0 if no parameters could be extracted (likely a modulus).
|
||||
*/
|
||||
static gboolean extract_ecc_params(const gchar *ekparams, gchar **ecc_x, gchar **ecc_y, gchar **ecc_curveid)
|
||||
{
|
||||
regmatch_t pmatch[5];
|
||||
regex_t preg;
|
||||
int ret;
|
||||
|
||||
if (regcomp(&preg, "x=([0-9A-Fa-f]+),y=([0-9A-Fa-f]+)(,id=([^,]+))?",
|
||||
REG_EXTENDED) != 0) {
|
||||
logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
if (regexec(&preg, ekparams, 5, pmatch, 0) == 0) {
|
||||
*ecc_x = g_strndup(&ekparams[pmatch[1].rm_so],
|
||||
pmatch[1].rm_eo - pmatch[1].rm_so);
|
||||
*ecc_y = g_strndup(&ekparams[pmatch[2].rm_so],
|
||||
pmatch[2].rm_eo - pmatch[2].rm_so);
|
||||
if (pmatch[4].rm_so > 0 && pmatch[4].rm_eo > 0)
|
||||
*ecc_curveid = g_strndup(&ekparams[pmatch[4].rm_so],
|
||||
pmatch[4].rm_eo - pmatch[4].rm_so);
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
regfree(&preg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the next serial number from the certserial file; if it contains
|
||||
* a non-numeric content start over with serial number '1'.
|
||||
*/
|
||||
static int get_next_serial(const gchar *certserial, const gchar *lockfile,
|
||||
gchar **serial_str)
|
||||
{
|
||||
g_autofree gchar *buffer = NULL;
|
||||
size_t buffer_len;
|
||||
unsigned long long serial, serial_n;
|
||||
char *endptr = NULL;
|
||||
int lockfd;
|
||||
int ret = 1;
|
||||
|
||||
lockfd = lock_file(lockfile);
|
||||
if (lockfd < 0)
|
||||
return 1;
|
||||
|
||||
if (access(certserial, R_OK) != 0)
|
||||
write_file(certserial, (unsigned char *)"1", 1);
|
||||
if (read_file(certserial, &buffer, &buffer_len) != 0)
|
||||
goto error;
|
||||
|
||||
if (buffer_len > 0) {
|
||||
serial = strtoull(buffer, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
serial_n = serial + 1;
|
||||
} else {
|
||||
serial_n = 1;
|
||||
}
|
||||
} else {
|
||||
serial_n = 1;
|
||||
}
|
||||
*serial_str = g_strdup_printf("%llu", serial_n);
|
||||
write_file(certserial, (unsigned char *)*serial_str, strlen(*serial_str));
|
||||
ret = 0;
|
||||
|
||||
error:
|
||||
unlock_file(lockfd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Create a TPM 1.2 or TPM 2 EK or platform cert */
|
||||
static int create_cert(unsigned long flags, const gchar *typ, const gchar *directory,
|
||||
gchar *ekparams, const gchar *vmid, gchar **tpm_spec_params,
|
||||
gchar **tpm_attr_params, const gchar *signkey,
|
||||
const gchar *signkey_password, const gchar *issuercert,
|
||||
const gchar *parentkey_password, gchar **swtpm_cert_env,
|
||||
const gchar *certserial, const gchar *lockfile,
|
||||
const gchar *optsfile)
|
||||
{
|
||||
gchar ** optsfile_lines = NULL;
|
||||
g_autofree gchar **options = NULL;
|
||||
g_autofree gchar **keyparams = NULL;
|
||||
g_autofree gchar **cmd = NULL;
|
||||
g_autofree gchar *subject = NULL;
|
||||
g_autofree gchar *ecc_x = NULL;
|
||||
g_autofree gchar *ecc_y = NULL;
|
||||
g_autofree gchar *ecc_curveid = NULL;
|
||||
g_autofree gchar *certfile = NULL;
|
||||
g_autofree gchar *serial_str = NULL;
|
||||
gchar **to_free = NULL;
|
||||
gchar **split;
|
||||
const char *certtype;
|
||||
int signkey_pwd_fd = -1;
|
||||
int parentkey_pwd_fd = -1;
|
||||
g_autofree gchar *signkey_pwd_file = NULL;
|
||||
g_autofree gchar *signkey_pwd_file_param = NULL;
|
||||
g_autofree gchar *parentkey_pwd_file = NULL;
|
||||
g_autofree gchar *parentkey_pwd_file_param = NULL;
|
||||
gboolean success;
|
||||
g_autofree gchar *standard_output = NULL;
|
||||
g_autofree gchar *standard_error = NULL;
|
||||
g_autofree gchar *swtpm_cert_path = NULL;
|
||||
GError *error = NULL;
|
||||
gint exit_status;
|
||||
int ret = 1;
|
||||
size_t i, j;
|
||||
|
||||
swtpm_cert_path = g_find_program_in_path("swtpm_cert");
|
||||
if (swtpm_cert_path == NULL) {
|
||||
logerr(gl_LOGFILE, "Could not find swtpm_cert in PATH.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (get_next_serial(certserial, lockfile, &serial_str) != 0)
|
||||
return 1;
|
||||
|
||||
/* try to read the optsfile */
|
||||
read_file_lines(optsfile, &optsfile_lines);
|
||||
|
||||
/* split each line from the optsfile and add the stripped parameters to options */
|
||||
for (i = 0; optsfile_lines != NULL && optsfile_lines[i] != NULL; i++) {
|
||||
gchar *chomped = g_strchomp(optsfile_lines[i]);
|
||||
if (strlen(chomped) == 0)
|
||||
continue;
|
||||
|
||||
split = g_strsplit(chomped, " ", -1);
|
||||
for (j = 0; split[j] != NULL; j++) {
|
||||
chomped = g_strchomp(split[j]);
|
||||
if (strlen(chomped) > 0) {
|
||||
gchar *to_add = g_strdup(chomped);
|
||||
options = concat_arrays(options, (gchar *[]){to_add, NULL}, TRUE);
|
||||
/* need to collect this also to free later on */
|
||||
to_free = concat_arrays(to_free, (gchar *[]){to_add, NULL}, TRUE);
|
||||
}
|
||||
}
|
||||
g_strfreev(split);
|
||||
}
|
||||
|
||||
if (vmid != NULL)
|
||||
subject = g_strdup_printf("CN=%s", vmid);
|
||||
else
|
||||
subject = g_strdup("CN=unknown");
|
||||
|
||||
if (flags & SETUP_TPM2_F)
|
||||
options = concat_arrays(options, (gchar *[]){"--tpm2", NULL}, TRUE);
|
||||
else
|
||||
options = concat_arrays(options, (gchar *[]){"--add-header", NULL}, TRUE);
|
||||
|
||||
if (strcmp(typ, "ek") == 0) {
|
||||
if (flags & ALLOW_SIGNING_F)
|
||||
options = concat_arrays(options, (gchar *[]){"--allow-signing", NULL}, TRUE);
|
||||
if (flags & DECRYPTION_F)
|
||||
options = concat_arrays(options, (gchar *[]){"--decryption", NULL}, TRUE);
|
||||
}
|
||||
|
||||
switch (extract_ecc_params(ekparams, &ecc_x, &ecc_y, &ecc_curveid)) {
|
||||
case 1:
|
||||
goto error;
|
||||
case 2:
|
||||
keyparams = concat_arrays((gchar *[]){
|
||||
"--ecc-x", ecc_x,
|
||||
"--ecc-y", ecc_y,
|
||||
NULL
|
||||
},
|
||||
NULL, FALSE);
|
||||
if (ecc_curveid != NULL)
|
||||
keyparams = concat_arrays(keyparams,
|
||||
(gchar *[]){
|
||||
"--ecc-curveid", ecc_curveid,
|
||||
NULL
|
||||
}, TRUE);
|
||||
break;
|
||||
case 0:
|
||||
keyparams = concat_arrays((gchar *[]){
|
||||
"--modulus", ekparams,
|
||||
NULL},
|
||||
NULL, FALSE);
|
||||
break;
|
||||
}
|
||||
|
||||
cmd = concat_arrays((gchar *[]){
|
||||
swtpm_cert_path, "--subject", subject, NULL
|
||||
}, options, FALSE);
|
||||
|
||||
if (signkey_password != NULL) {
|
||||
signkey_pwd_fd = write_to_tempfile(&signkey_pwd_file,
|
||||
(unsigned char *)signkey_password, strlen(signkey_password));
|
||||
if (signkey_pwd_fd < 0)
|
||||
goto error;
|
||||
|
||||
signkey_pwd_file_param = g_strdup_printf("file:%s", signkey_pwd_file);
|
||||
cmd = concat_arrays(cmd, (gchar*[]){"--signkey-pwd", signkey_pwd_file_param, NULL}, TRUE);
|
||||
}
|
||||
if (parentkey_password != NULL) {
|
||||
parentkey_pwd_fd = write_to_tempfile(&parentkey_pwd_file,
|
||||
(unsigned char *)parentkey_password, strlen(parentkey_password));
|
||||
if (parentkey_pwd_fd < 0)
|
||||
goto error;
|
||||
|
||||
parentkey_pwd_file_param = g_strdup_printf("file:%s", parentkey_pwd_file);
|
||||
cmd = concat_arrays(cmd, (gchar*[]){"--parentkey-pwd", parentkey_pwd_file_param, NULL}, TRUE);
|
||||
}
|
||||
|
||||
if (strcmp(typ, "ek") == 0)
|
||||
cmd = concat_arrays(cmd, tpm_spec_params, TRUE);
|
||||
|
||||
cmd = concat_arrays(cmd, tpm_attr_params, TRUE);
|
||||
|
||||
if (strcmp(typ, "platform") == 0) {
|
||||
certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "platform.cert", NULL);
|
||||
cmd = concat_arrays(cmd,
|
||||
(gchar *[]){
|
||||
"--type", "platform",
|
||||
"--out-cert", certfile,
|
||||
NULL},
|
||||
TRUE);
|
||||
} else {
|
||||
certfile = g_strjoin(G_DIR_SEPARATOR_S, directory, "ek.cert", NULL);
|
||||
cmd = concat_arrays(cmd,
|
||||
(gchar *[]){
|
||||
"--out-cert", certfile,
|
||||
NULL
|
||||
}, TRUE);
|
||||
}
|
||||
|
||||
cmd = concat_arrays(cmd, keyparams, TRUE);
|
||||
cmd = concat_arrays(cmd, (gchar *[]){
|
||||
"--signkey", (gchar *)signkey,
|
||||
"--issuercert", (gchar *)issuercert,
|
||||
"--days", "7300",
|
||||
"--serial", (gchar *)serial_str,
|
||||
NULL
|
||||
}, TRUE);
|
||||
|
||||
if (strcmp(typ, "ek") == 0)
|
||||
certtype = "EK";
|
||||
else
|
||||
certtype = "platform";
|
||||
#if 0
|
||||
{
|
||||
g_autofree gchar *join = g_strjoinv(" ", cmd);
|
||||
fprintf(stderr, "Starting: %s\n", join);
|
||||
}
|
||||
#endif
|
||||
success = g_spawn_sync(NULL, cmd, swtpm_cert_env, G_SPAWN_DEFAULT, NULL, NULL,
|
||||
&standard_output, &standard_error, &exit_status, &error);
|
||||
if (!success) {
|
||||
logerr(gl_LOGFILE, "Could not run swtpm_cert: %s\n", error);
|
||||
g_error_free(error);
|
||||
goto error;
|
||||
}
|
||||
if (exit_status != 0) {
|
||||
logerr(gl_LOGFILE, "Could not create %s certificate locally\n", certtype);
|
||||
logerr(gl_LOGFILE, "%s\n", standard_error);
|
||||
goto error;
|
||||
}
|
||||
|
||||
logit(gl_LOGFILE, "Successfully created %s certificate locally.\n", certtype);
|
||||
ret = 0;
|
||||
|
||||
error:
|
||||
g_strfreev(optsfile_lines);
|
||||
g_strfreev(to_free);
|
||||
|
||||
if (signkey_pwd_fd >= 0)
|
||||
close(signkey_pwd_fd);
|
||||
if (signkey_pwd_file)
|
||||
unlink(signkey_pwd_file);
|
||||
|
||||
if (parentkey_pwd_fd >= 0)
|
||||
close(parentkey_pwd_fd);
|
||||
if (parentkey_pwd_file)
|
||||
unlink(parentkey_pwd_file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usage(const char *prgname)
|
||||
{
|
||||
printf(
|
||||
"Usage: %s [options]\n"
|
||||
"\n"
|
||||
"The following options are supported:\n"
|
||||
"\n"
|
||||
"--type type The type of certificate to create: 'ek' or 'platform'\n"
|
||||
"--ek key-param The modulus of an RSA key or x=...,y=,... for an EC key\n"
|
||||
"--dir directory The directory to write the resulting certificate into\n"
|
||||
"--vmid vmid The ID of the virtual machine\n"
|
||||
"--optsfile file A file containing options to pass to swtpm_cert\n"
|
||||
"--configfile file A file containing configuration parameters for directory,\n"
|
||||
" signing key and password and certificate to use\n"
|
||||
"--logfile file A file to write a log into\n"
|
||||
"--tpm-spec-family s The implemented spec family, e.g., '2.0'\n"
|
||||
"--tpm-spec-revision i The spec revision of the TPM as integer; e.g., 146\n"
|
||||
"--tpm-spec-level i The spec level of the TPM; must be an integer; e.g. 0\n"
|
||||
"--tpm-manufacturer s The manufacturer of the TPM; e.g., id:00001014\n"
|
||||
"--tpm-model s The model of the TPM; e.g., 'swtpm'\n"
|
||||
"--tpm-version i The (firmware) version of the TPM; e.g., id:20160511\n"
|
||||
"--tpm2 Generate a certificate for a TPM 2\n"
|
||||
"--allow-signing The TPM 2's EK can be used for signing\n"
|
||||
"--decryption The TPM 2's EK can be used for decryption\n"
|
||||
"--help, -h, -? Display this help screen and exit\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"The following environment variables are supported:\n"
|
||||
"\n"
|
||||
"SWTPM_ROOTCA_PASSWORD The root CA's private key password\n"
|
||||
"\n", prgname);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int opt, option_index = 0;
|
||||
const static struct option long_options[] = {
|
||||
{"type", required_argument, NULL, 't'},
|
||||
{"ek", required_argument, NULL, 'e'},
|
||||
{"dir", required_argument, NULL, 'd'},
|
||||
{"vmid", required_argument, NULL, 'v'},
|
||||
{"optsfile", required_argument, NULL, 'o'},
|
||||
{"configfile", required_argument, NULL, 'c'},
|
||||
{"logfile", required_argument, NULL, 'l'},
|
||||
{"tpm-spec-family", required_argument, NULL, 'f'},
|
||||
{"tpm-spec-revision", required_argument, NULL, 'r'},
|
||||
{"tpm-spec-level", required_argument, NULL, '1'},
|
||||
{"tpm-manufacturer", required_argument, NULL, 'a'},
|
||||
{"tpm-model", required_argument, NULL, 'm'},
|
||||
{"tpm-version", required_argument, NULL, 's'},
|
||||
{"tpm2", no_argument, NULL, '2'},
|
||||
{"allow-signing", no_argument, NULL, 'i'},
|
||||
{"decryption", no_argument, NULL, 'y'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
};
|
||||
g_autofree gchar *default_options_file = NULL;
|
||||
g_autofree gchar *default_config_file = NULL;
|
||||
g_autofree gchar *optsfile = NULL;
|
||||
g_autofree gchar *configfile = NULL;
|
||||
unsigned long flags = 0;
|
||||
g_autofree gchar *typ =g_strdup("");
|
||||
g_autofree gchar *ekparams = g_strdup("");
|
||||
g_autofree gchar *directory = g_strdup("."); /* default to current directory */
|
||||
g_autofree gchar *vmid = NULL;
|
||||
g_autofree gchar *lockfile = NULL;
|
||||
g_autofree gchar *statedir = NULL;
|
||||
g_autofree gchar *signkey = NULL;
|
||||
g_autofree gchar *signkey_password = NULL;
|
||||
g_autofree gchar *parentkey_password = NULL;
|
||||
g_autofree gchar *issuercert = NULL;
|
||||
g_autofree gchar *certserial = NULL;
|
||||
gchar **tpm_spec_params = NULL;
|
||||
gchar **tpm_attr_params = NULL;
|
||||
gchar **config_file_lines = NULL;
|
||||
gchar **swtpm_cert_env = NULL;
|
||||
const struct passwd *curr_user;
|
||||
struct stat statbuf;
|
||||
int ret = 1;
|
||||
|
||||
if (init(&default_options_file, &default_config_file) < 0)
|
||||
goto error;
|
||||
optsfile = g_strdup(default_options_file);
|
||||
configfile = g_strdup(default_config_file);
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "h?",
|
||||
long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 't': /* --type */
|
||||
g_free(typ);
|
||||
typ = g_strdup(optarg);
|
||||
break;
|
||||
case 'e': /* --ek */
|
||||
g_free(ekparams);
|
||||
ekparams = g_strdup(optarg);
|
||||
break;
|
||||
case 'd': /* --dir */
|
||||
g_free(directory);
|
||||
directory = g_strdup(optarg);
|
||||
break;
|
||||
case 'v': /* --vmid */
|
||||
g_free(vmid);
|
||||
vmid = g_strdup(optarg);
|
||||
break;
|
||||
case 'o': /* --optsfile */
|
||||
g_free(optsfile);
|
||||
optsfile = g_strdup(optarg);
|
||||
break;
|
||||
case 'c': /* --configfile */
|
||||
g_free(configfile);
|
||||
configfile = g_strdup(optarg);
|
||||
break;
|
||||
case 'l': /* --logfile */
|
||||
g_free(gl_LOGFILE);
|
||||
gl_LOGFILE = g_strdup(optarg);
|
||||
break;
|
||||
case 'f': /* --tpm-spec-family */
|
||||
case 'r': /* --tpm-spec-revision */
|
||||
case '1': /* --tpm-spec-level */
|
||||
tpm_spec_params = concat_arrays(tpm_spec_params,
|
||||
(gchar *[]) {
|
||||
g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL
|
||||
}, TRUE);
|
||||
break;
|
||||
case 'a': /* --tpm-manufacturer */
|
||||
case 'm': /* --tpm-model */
|
||||
case 's': /* --tpm-version */
|
||||
tpm_attr_params = concat_arrays(tpm_attr_params,
|
||||
(gchar *[]) {
|
||||
g_strdup_printf("--%s", long_options[option_index].name), g_strdup(optarg), NULL
|
||||
}, TRUE);
|
||||
break;
|
||||
case '2': /* --tpm2 */
|
||||
flags |= SETUP_TPM2_F;
|
||||
break;
|
||||
case 'i': /* --allow-signing */
|
||||
flags |= ALLOW_SIGNING_F;
|
||||
break;
|
||||
case 'y': /* --decryption */
|
||||
flags |= DECRYPTION_F;
|
||||
break;
|
||||
case '?':
|
||||
case 'h': /* --help */
|
||||
usage(argv[0]);
|
||||
ret = 0;
|
||||
goto out;
|
||||
default:
|
||||
fprintf(stderr, "Unknown option code %d\n", opt);
|
||||
usage(argv[0]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
curr_user = getpwuid(getuid());
|
||||
|
||||
if (gl_LOGFILE != NULL) {
|
||||
FILE *tmpfile;
|
||||
|
||||
if (stat(gl_LOGFILE, &statbuf) == 0 &&
|
||||
(statbuf.st_mode & S_IFMT) == S_IFLNK) {
|
||||
fprintf(stderr, "Logfile must not be a symlink.\n");
|
||||
goto error;
|
||||
}
|
||||
tmpfile = fopen(gl_LOGFILE, "a"); // do not truncate
|
||||
if (tmpfile == NULL) {
|
||||
fprintf(stderr, "Cannot write to logfile %s.\n", gl_LOGFILE);
|
||||
goto error;
|
||||
}
|
||||
fclose(tmpfile);
|
||||
}
|
||||
|
||||
if (access(optsfile, R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read rights on options file %s for user %s.\n",
|
||||
optsfile, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (access(configfile, R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read rights on config file %s for user %s.\n",
|
||||
configfile, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (read_file_lines(configfile, &config_file_lines) != 0)
|
||||
goto error;
|
||||
|
||||
statedir = get_config_value(config_file_lines, "statedir", NULL);
|
||||
if (statedir == NULL) {
|
||||
logerr(gl_LOGFILE, "Missing 'statedir' config value in config file %s.\n", configfile);
|
||||
goto error;
|
||||
}
|
||||
if (makedir(statedir, "statedir") != 0)
|
||||
goto error;
|
||||
if (access(statedir, W_OK | R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read/write rights on statedir %s for user %s.\n",
|
||||
statedir, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
lockfile = g_strjoin(G_DIR_SEPARATOR_S, statedir, ".lock.swtpm-localca", NULL);
|
||||
if (stat(lockfile, &statbuf) == 0 &&
|
||||
access(lockfile, W_OK | R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read/write rights on %s for user %s.\n",
|
||||
lockfile, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
signkey = get_config_value(config_file_lines, "signingkey", NULL);
|
||||
if (signkey == NULL) {
|
||||
logerr(gl_LOGFILE, "Missing 'signingkey' config value in config file %s.\n",
|
||||
configfile);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!g_str_has_prefix(signkey, "tpmkey:file=") &&
|
||||
!g_str_has_prefix(signkey, "tpmkey:uuid=") &&
|
||||
!g_str_has_prefix(signkey, "pkcs11:")) {
|
||||
g_autofree gchar *d = g_path_get_dirname(signkey);
|
||||
if (makedir(d, "signkey") != 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
signkey_password = get_config_value(config_file_lines, "signingkey_password", NULL);
|
||||
parentkey_password = get_config_value(config_file_lines, "parentkey_password", NULL);
|
||||
|
||||
issuercert = get_config_value(config_file_lines, "issuercert", NULL);
|
||||
if (issuercert == NULL) {
|
||||
logerr(gl_LOGFILE, "Missing 'issuercert' config value in config file %s.\n", configfile);
|
||||
goto error;
|
||||
}
|
||||
{
|
||||
g_autofree gchar *d = g_path_get_dirname(issuercert);
|
||||
if (makedir(d, "issuercert") != 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
swtpm_cert_env = g_get_environ();
|
||||
|
||||
// TPM keys are GNUTLS URIs...
|
||||
if (g_str_has_prefix(signkey, "tpmkey:file=") || g_str_has_prefix(signkey, "tpmkey:uuid=")) {
|
||||
gchar *tss_tcsd_hostname = get_config_value(config_file_lines,
|
||||
"TSS_TCSD_HOSTNAME", "localhost");
|
||||
gchar *tss_tcsd_port = get_config_value(config_file_lines,
|
||||
"TSS_TCSD_PORT", "30003");
|
||||
swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
|
||||
"TSS_TCSD_HOSTNAME", tss_tcsd_hostname, TRUE);
|
||||
swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
|
||||
"TSS_TCSD_PORT", tss_tcsd_port, TRUE);
|
||||
|
||||
logit(gl_LOGFILE, "CA uses a GnuTLS TPM key; using TSS_TCSD_HOSTNAME=%s " \
|
||||
"TSS_TCSD_PORT=%s\n", tss_tcsd_hostname, tss_tcsd_port);
|
||||
} else if (g_str_has_prefix(signkey, "pkcs11:")) {
|
||||
gchar *tmp = str_replace(signkey, "\\;", ";"); /* historical reasons ... */
|
||||
g_free(signkey);
|
||||
signkey = tmp;
|
||||
|
||||
if (signkey_password != NULL) {
|
||||
swtpm_cert_env = g_environ_setenv(swtpm_cert_env,
|
||||
"SWTPM_PKCS11_PIN", g_strdup(signkey_password), TRUE);
|
||||
logit(gl_LOGFILE, "CA uses a PKCS#11 key; using SWTPM_PKCSS1_PIN\n");
|
||||
}
|
||||
ret = get_config_envvars(config_file_lines, &swtpm_cert_env);
|
||||
if (ret != 0)
|
||||
goto error;
|
||||
} else {
|
||||
if (access(signkey, R_OK) != 0) {
|
||||
if (stat(signkey, &statbuf) == 0) {
|
||||
logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n",
|
||||
signkey, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
logit(gl_LOGFILE, "Creating root CA and a local CA's signing key and issuer cert.\n");
|
||||
if (create_localca_cert(lockfile, statedir, signkey, signkey_password,
|
||||
issuercert) != 0) {
|
||||
logerr(gl_LOGFILE, "Error creating local CA's signing key and cert.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (access(signkey, R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read rights on signing key %s for user %s.\n",
|
||||
signkey, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(issuercert, R_OK) != 0) {
|
||||
logerr(gl_LOGFILE, "Need read rights on issuer certificate %s for user %s.\n",
|
||||
issuercert, curr_user ? curr_user->pw_name : "<unknown>");
|
||||
goto error;
|
||||
}
|
||||
|
||||
{
|
||||
g_autofree gchar *d = NULL;
|
||||
g_autofree gchar *p = g_strjoin(G_DIR_SEPARATOR_S, statedir, "certserial", NULL);
|
||||
|
||||
certserial = get_config_value(config_file_lines, "certserial", p);
|
||||
d = g_path_get_dirname(certserial);
|
||||
if (makedir(d, "certserial") != 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = create_cert(flags, typ, directory, ekparams, vmid, tpm_spec_params, tpm_attr_params,
|
||||
signkey, signkey_password, issuercert, parentkey_password, swtpm_cert_env,
|
||||
certserial, lockfile, optsfile);
|
||||
|
||||
out:
|
||||
error:
|
||||
g_strfreev(tpm_attr_params);
|
||||
g_strfreev(tpm_spec_params);
|
||||
|
||||
exit(ret);
|
||||
}
|
||||
17
samples/swtpm_localca.h
Normal file
17
samples/swtpm_localca.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* swtpm_localca.h: Header file
|
||||
*
|
||||
* Author: Stefan Berger, stefanb@linux.ibm.com
|
||||
*
|
||||
* Copyright (c) IBM Corporation, 2021
|
||||
*/
|
||||
|
||||
#ifndef SWTPM_LOCALCA_H
|
||||
#define SWTPM_LOCALCA_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
extern gchar *gl_LOGFILE;
|
||||
|
||||
#endif /* SWTPM_LOCALCA_H */
|
||||
15
samples/swtpm_localca_conf.h.in
Normal file
15
samples/swtpm_localca_conf.h.in
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* swtpm_setup_conf.h: Compile-time constants
|
||||
*
|
||||
* Author: Stefan Berger, stefanb@linux.ibm.com
|
||||
*
|
||||
* Copyright (c) IBM Corporation, 2021
|
||||
*/
|
||||
|
||||
#ifndef SWTPM_LOCALCA_CONF_H
|
||||
#define SWTPM_LOCALCA_CONF_H
|
||||
|
||||
#define SYSCONFDIR "@SYSCONFDIR@"
|
||||
|
||||
#endif /* SWTPM_LOCALCA_CONF_H */
|
||||
139
samples/swtpm_localca_utils.c
Normal file
139
samples/swtpm_localca_utils.c
Normal file
@ -0,0 +1,139 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* swtpm_localca_utils.c: Utility functions
|
||||
*
|
||||
* Author: Stefan Berger, stefanb@linux.ibm.com
|
||||
*
|
||||
* Copyright (c) IBM Corporation, 2021
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <regex.h>
|
||||
#include <errno.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "swtpm_utils.h"
|
||||
#include "swtpm_localca.h"
|
||||
#include "swtpm_localca_utils.h"
|
||||
|
||||
/* Create a directory pat (and all its predecessors) if it doesn't exist */
|
||||
int makedir(const char *dirname, const char *purpose)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (stat(dirname, &statbuf) != 0) {
|
||||
logit(gl_LOGFILE, "Creating swtpm-localca dir '%s'.\n", dirname);
|
||||
if (g_mkdir_with_parents(dirname, S_IRWXU | S_IRWXG | S_IXGRP | S_IRGRP) == -1) {
|
||||
logerr(gl_LOGFILE, "Could not create directory for '%s': %s\n",
|
||||
purpose, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get a configuration value given its name */
|
||||
gchar *get_config_value(gchar **config_file_lines, const gchar *configname, const gchar *fallback)
|
||||
{
|
||||
g_autofree gchar *regex = g_strdup_printf("^%s[[:space:]]*=[[:space:]]*([^#\n]*).*", configname);
|
||||
gchar *result = NULL;
|
||||
regex_t preg;
|
||||
size_t idx;
|
||||
regmatch_t pmatch[2];
|
||||
|
||||
if (regcomp(&preg, regex, REG_EXTENDED) != 0) {
|
||||
logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (idx = 0; config_file_lines[idx] != NULL; idx++) {
|
||||
const gchar *line = config_file_lines[idx];
|
||||
if (regexec(&preg, line, 2, pmatch, 0) == 0) {
|
||||
result = g_strndup(&line[pmatch[1].rm_so],
|
||||
pmatch[1].rm_eo - pmatch[1].rm_so);
|
||||
/* coverity: g_strchmop modifies in-place */
|
||||
result = g_strchomp(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
regfree(&preg);
|
||||
|
||||
error:
|
||||
if (result == NULL)
|
||||
result = g_strdup(fallback);
|
||||
//printf("Found match for %s: |%s|\n", configname, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Extract all environment variables from the config file and add them to
|
||||
* the given environent.
|
||||
* Environment variable lines must start with 'env:' and must not contain
|
||||
* trailing spaces or a comment starting with '#'
|
||||
*/
|
||||
int get_config_envvars(gchar **config_file_lines, gchar ***env)
|
||||
{
|
||||
const char *regex = "^env:([a-zA-Z_][a-zA-Z_0-9]*)[[:space:]]*=[[:space:]]*([^\n]*)";
|
||||
regex_t preg;
|
||||
size_t idx;
|
||||
regmatch_t pmatch[3];
|
||||
|
||||
if (regcomp(&preg, regex, REG_EXTENDED) != 0) {
|
||||
logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (idx = 0; config_file_lines[idx] != NULL; idx++) {
|
||||
const gchar *line = config_file_lines[idx];
|
||||
if (regexec(&preg, line, 3, pmatch, 0) == 0) {
|
||||
g_autofree gchar *key, *value;
|
||||
|
||||
key = g_strndup(&line[pmatch[1].rm_so],
|
||||
pmatch[1].rm_eo - pmatch[1].rm_so);
|
||||
value = g_strndup(&line[pmatch[2].rm_so],
|
||||
pmatch[2].rm_eo - pmatch[2].rm_so);
|
||||
*env = g_environ_setenv(*env, key, value, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
regfree(&preg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* flock a file; the file descriptor for the file to unlock later on is returned */
|
||||
int lock_file(const gchar *lockfile)
|
||||
{
|
||||
int lockfd;
|
||||
mode_t mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
|
||||
|
||||
lockfd = open(lockfile, O_RDWR | O_CREAT, mode);
|
||||
if (lockfd < 0) {
|
||||
logerr(gl_LOGFILE, "Could not open lockfile %s: %s\n", lockfile, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flock(lockfd, LOCK_EX) < 0) {
|
||||
logerr(gl_LOGFILE, "Could not lock file %s: %s\n", lockfile, strerror(errno));
|
||||
close(lockfd);
|
||||
return -1;
|
||||
}
|
||||
return lockfd;
|
||||
}
|
||||
|
||||
/* unlock a file previously locked using lock_file */
|
||||
void unlock_file(int lockfd) {
|
||||
if (lockfd >= 0) {
|
||||
flock(lockfd, LOCK_UN);
|
||||
close(lockfd);
|
||||
}
|
||||
}
|
||||
23
samples/swtpm_localca_utils.h
Normal file
23
samples/swtpm_localca_utils.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* swtpm_localca_utils.h: Header file for swtpm_localca_utils.c
|
||||
*
|
||||
* Author: Stefan Berger, stefanb@linux.ibm.com
|
||||
*
|
||||
* Copyright (c) IBM Corporation, 2021
|
||||
*/
|
||||
|
||||
#ifndef SWTPM_LOCALCA_UTILS_H
|
||||
#define SWTPM_LOCALCA_UTILS_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
gchar *get_config_value(gchar **config_file_lines, const gchar *varname, const gchar *fallback);
|
||||
int get_config_envvars(gchar **config_file_lines, gchar ***env);
|
||||
|
||||
int makedir(const char *dirname, const char *purpose);
|
||||
|
||||
int lock_file(const gchar *lockfile);
|
||||
void unlock_file(int lockfd);
|
||||
|
||||
#endif /* SWTPM_LOCALCA_UTILS_H */
|
||||
@ -321,3 +321,106 @@ int read_file_lines(const char *filename, gchar ***config_file_lines)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t write_to_fd(int fd, const unsigned char *data, size_t data_len)
|
||||
{
|
||||
ssize_t n;
|
||||
|
||||
n = write(fd, data, data_len);
|
||||
if (n < 0) {
|
||||
logerr(gl_LOGFILE, "Could not write to file: %s\n", strerror(errno));
|
||||
} else if ((size_t)n != data_len) {
|
||||
logerr(gl_LOGFILE, "Could not write all bytes to the file.\n");
|
||||
n = -1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Write to a file with the given name */
|
||||
int write_file(const gchar *filename, const unsigned char *data, size_t data_len)
|
||||
{
|
||||
ssize_t n;
|
||||
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
|
||||
if (fd < 0) {
|
||||
logerr(gl_LOGFILE, "Could not open file %s for writing: %s\n",
|
||||
filename, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
n = write_to_fd(fd, data, data_len);
|
||||
close(fd);
|
||||
if (n < 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int write_to_tempfile(gchar **filename, const unsigned char *data, size_t data_len)
|
||||
{
|
||||
GError *error = NULL;
|
||||
int fd = g_file_open_tmp("XXXXXX", filename, &error);
|
||||
ssize_t n;
|
||||
|
||||
if (error) {
|
||||
logerr(gl_LOGFILE, "Could not create temporary file: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
n = write_to_fd(fd, data, data_len);
|
||||
if (n < 0)
|
||||
goto error;
|
||||
|
||||
n = lseek(fd, 0, SEEK_SET);
|
||||
if (n < 0) {
|
||||
logerr(gl_LOGFILE, "Could not seek(0) on file '%s': %s\n", filename, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
return fd;
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* replace occurences of 'torep' with 'rep' in a string 'in' */
|
||||
gchar *str_replace(const char *in, const char *torep, const char *rep)
|
||||
{
|
||||
char *res;
|
||||
const char *s, *b;
|
||||
size_t torep_len;
|
||||
size_t rep_len;
|
||||
size_t ctr = 0;
|
||||
size_t off = 0;
|
||||
|
||||
if (in == NULL || torep == NULL || rep == NULL)
|
||||
return NULL;
|
||||
|
||||
torep_len = strlen(torep);
|
||||
if (torep_len == 0)
|
||||
return NULL;
|
||||
|
||||
rep_len = strlen(rep);
|
||||
|
||||
s = in;
|
||||
while ((s = strstr(s, torep)) != NULL) {
|
||||
s += torep_len;
|
||||
ctr++;
|
||||
}
|
||||
|
||||
res = g_malloc(strlen(in) - ctr * torep_len + ctr * rep_len + 1);
|
||||
|
||||
b = s = in;
|
||||
while ((s = strstr(s, torep)) != NULL) {
|
||||
strncpy(&res[off], b, s - b);
|
||||
off += (s - b);
|
||||
s += torep_len;
|
||||
b = s;
|
||||
strcpy(&res[off], rep);
|
||||
off += rep_len;
|
||||
}
|
||||
strcpy(&res[off], b);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -33,4 +33,9 @@ gchar **split_cmdline(const gchar *cmdline);
|
||||
int read_file(const gchar *filename, gchar **buffer, gsize *buffer_len);
|
||||
int read_file_lines(const char *filename, gchar ***config_file_lines);
|
||||
|
||||
int write_file(const gchar *filename, const unsigned char *data, size_t data_len);
|
||||
int write_to_tempfile(gchar **filename, const unsigned char *data, size_t data_len);
|
||||
|
||||
gchar *str_replace(const char *in, const char *torep, const char *rep);
|
||||
|
||||
#endif /* SWTPM_UTILS_H */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user