Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TPM Key Support #74

Merged
merged 31 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6e5e91b
Add stub of TPMv2 support
dwmw2 Jun 12, 2023
3610922
Flesh out unimplemented TPMv2Signer a little more
dwmw2 Jun 14, 2023
a17714e
Basic shell of TPM, working at least for EC
dwmw2 Jun 14, 2023
eda60b6
Add RSA support
dwmw2 Jun 15, 2023
72ead0c
Add TPM support to docs
dwmw2 Jun 15, 2023
77a97d5
Add tests for TPM
dwmw2 Jul 3, 2023
5d35347
Allow $TPM_DEVICE to set TPM device path
dwmw2 Jul 4, 2023
908569c
Add swtpm testing
dwmw2 Jul 4, 2023
2944557
Test cert + key both in one PEM file
dwmw2 Jul 4, 2023
42afaef
Merge in dwmw2:tpm
13ajay Jun 12, 2024
e785ffd
Merge branch 'dwmw2-tpm' into pkcs11
13ajay Jun 12, 2024
7767d1d
Support password-protected TPM signing keys
13ajay Jun 26, 2024
aca9860
Add testing for RSA TPM keys that have the Sign capability
13ajay Jun 26, 2024
5cec079
Code formatting and clean up
13ajay Jun 26, 2024
38e0412
Add further documentation around TPM keys
13ajay Jun 26, 2024
cceb33d
Add TPM capability check before signing
13ajay Jun 27, 2024
085de24
Add parent key password support
13ajay Jun 28, 2024
2b1ea32
Add tests to verify parent key password support
13ajay Aug 11, 2024
8a09473
Fix builds on Windows
13ajay Sep 6, 2024
dfbcf65
Introduce flags to convey intent when not using TPM key passwords
13ajay Sep 19, 2024
70904cc
Add some more tests for TPMv2Signer
13ajay Sep 19, 2024
7bdd7a9
Accept TPM key handles
13ajay Oct 17, 2024
0694dfc
Switch Makefile to using Intel tools to generate TPM fixtures
13ajay Oct 18, 2024
54968db
Determine authorization requirement based on CLI flags or key file
13ajay Oct 19, 2024
d6c3740
Improve TPM key guidance
13ajay Oct 19, 2024
a17522f
Fix 'scripts' section of README
13ajay Oct 23, 2024
574140c
Modify TPM key file password indication behavior
13ajay Oct 24, 2024
8ac0e84
Miscellaneous changes
13ajay Nov 6, 2024
19bb307
Error out when TPM key files are used on Windows
13ajay Nov 6, 2024
15598e3
Merge branch 'main' into tpm
13ajay Nov 6, 2024
d937441
Update THIRD-PARTY-LICENSES.txt
13ajay Nov 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 129 additions & 27 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,90 @@ ECCERTS := $(foreach digest, sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %-
RSACERTS := $(foreach digest, md5 sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %-$(digest)-cert.pem, $(RSAKEYS)))
PKCS12CERTS := $(patsubst %-cert.pem, %.p12, $(RSACERTS) $(ECCERTS))

# It's hard to do a file-based rule for the contents of the SoftHSM token.
# Software TPM. For generating keys/certs, we run the swtpm in TCP mode,
# because that's what the tools and the OpenSSL ENGINE require. Each of
# the rules which might need the swtpm will ensure that it's running by
# invoking $(START_SWTPM_TCP). The 'user-certs:; rule will then *stop* it
# by running $(STOP_SWTPM_TCP), after all the certs and keys have been
# created.
#
# For the actual test, we need it to run in UNIX socket mode, since
# *that* is all that go-tpm can cope with. So we start it in that mode
# in the 'test:' recipe, and stop it again afterwards.
#
SWTPM_STATEDIR := $(curdir)/tst/swtpm
SWTPM_CTRLSOCK := $(curdir)/tst/swtpm-ctrl
SWTPM_SERVSOCK := $(curdir)/tst/swtpm-serv
SWTPM := swtpm socket --tpm2 --tpmstate dir=$(SWTPM_STATEDIR)

# Annoyingly, while we only support UNIX socket, the ENGINE only supports TCP.
SWTPM_UNIX := --server type=unixio,path=$(SWTPM_SERVSOCK) --ctrl type=unixio,path=$(SWTPM_CTRLSOCK)
SWTPM_NET := --server type=tcp,port=2321 --ctrl type=tcp,port=2322

# Check that the swtpm is running for TCP connections. This isn't a normal
# phony rule because we don't want it running unless there's actually some
# work to be done over the TCP socket (creating keys, certs, etc.).
START_SWTPM_TCP := \
if ! swtpm_ioctl --tcp 127.0.0.1:2322 -g >/dev/null 2>/dev/null; then \
mkdir -p $(SWTPM_STATEDIR); \
$(SWTPM) $(SWTPM_NET) --flags not-need-init,startup-clear -d; \
fi
STOP_SWTPM_TCP := swtpm_ioctl --tcp 127.0.0.1:2322 -s

# This one is used for the actual test run
START_SWTPM_UNIX := \
if ! swtpm_ioctl --unix $(SWTPM_CTRLSOCK) -g >/dev/null 2>/dev/null; then \
$(SWTPM) $(SWTPM_UNIX) --flags not-need-init,startup-clear -d; \
fi
STOP_SWTPM_UNIX := swtpm_ioctl --unix $(SWTPM_CTRLSOCK) -s

$(certsdir)/tpm-sw-rsa-key.pem:
$(START_SWTPM_TCP)
TPM_INTERFACE_TYPE=socsim create_tpm2_key -r $@

$(certsdir)/tpm-sw-ec-prime256-key.pem:
$(START_SWTPM_TCP)
TPM_INTERFACE_TYPE=socsim create_tpm2_key -e prime256v1 $@

$(certsdir)/tpm-sw-ec-secp384r1-key.pem:
$(START_SWTPM_TCP)
TPM_INTERFACE_TYPE=socsim create_tpm2_key -e secp384r1 $@

# Create a persistent key at 0x81000001 in the owner hierarchiy, if it
# doesn't already exist. And a PEM key with that as its parent.
$(certsdir)/tpm-sw-ec-81000001-key.pem:
$(START_SWTPM_TCP)
if ! TPM_INTERFACE_TYPE=socsim tssreadpublic -ho 81000001; then \
TPM_INTERFACE_TYPE=socsim tsscreateprimary -hi o -rsa && \
TPM_INTERFACE_TYPE=socsim tssevictcontrol -hi o -ho 80000000 -hp 81000001; \
fi
TPM_INTERFACE_TYPE=socsim create_tpm2_key -e prime256v1 -p 81000001 $@

SWTPMKEYS := $(certsdir)/tpm-sw-rsa-key.pem $(certsdir)/tpm-sw-ec-secp384r1-key.pem $(certsdir)/tpm-sw-ec-prime256-key.pem $(certsdir)/tpm-sw-ec-81000001-key.pem
SWTPMCERTS := $(foreach digest, sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %-$(digest)-cert.pem, $(SWTPMKEYS)))

HWTPMKEYS := $(certsdir)/tpm-hw-rsa-key.pem $(certsdir)/tpm-hw-ec-key.pem $(certsdir)/tpm-hw-ec-81000001-key.pem
HWTPMCERTS := $(foreach digest, sha1 sha256 sha384 sha512, $(patsubst %-key.pem, %-$(digest)-cert.pem, $(HWTPMKEYS)))

# User can test on hardware TPM with `make TPM_DEVICE=/dev/tpmrm0 test`
ifeq ($(TPM_DEVICE),)
TPM_DEVICE := $(SWTPM_SERVSOCK)
TPMKEYS := $(SWTPMKEYS)
TPMCERTS := $(SWTPMCERTS)
START_SWTPM := $(START_SWTPM_UNIX)
STOP_SWTPM := $(STOP_SWTPM_UNIX)
else
TPMKEYS := $(HWTPMKEYS)
TPMCERTS := $(HWTPMCERTS)
START_SWTPM := true
STOP_SWTPM := true
endif

export TPM_DEVICE



# It's hard to ao a file-based rule for the contents of the SoftHSM token.
# So just populate it as a side-effect of creating the softhsm2.conf file.
tst/softhsm2.conf: tst/softhsm2.conf.template $(PKCS8KEYS) $(RSACERTS) $(ECCERTS) tst/certs/rsa-2048-2-sha256-cert.pem
rm -rf tst/softhsm/*
Expand Down Expand Up @@ -59,31 +142,30 @@ tst/softhsm2.conf: tst/softhsm2.conf.template $(PKCS8KEYS) $(RSACERTS) $(ECCERTS

.PHONY: test
test: test-certs tst/softhsm2.conf
SOFTHSM2_CONF=$(curdir)/tst/softhsm2.conf go test -v ./...

%-md5-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -md5
%-sha1-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -sha1
%-sha256-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -sha256
%-2-sha256-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -sha256
%-sha384-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -sha384
%-sha512-cert.pem: %-key.pem
SUBJ=$$(echo "$@" | sed -r 's|.*/([^/]+)-cert.pem|\1|'); \
openssl req -x509 -new -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -sha512
$(START_SWTPM)
SOFTHSM2_CONF=$(curdir)/tst/softhsm2.conf go test -v ./... || :
$(STOP_SWTPM)

define CERT_RECIPE
@SUBJ=$$(echo "$@" | sed 's^\(.*/\)\?\([^/]*\)-cert.pem^\2^'); \
[ "$${SUBJ#tpm-}" != "$${SUBJ}" ] && ENG="--engine tpm2 --keyform engine"; \
if [ "$${SUBJ#tpm-sw-}" != "$${SUBJ}" ]; then $(START_SWTPM_TCP); export TPM_INTERFACE_TYPE=socsim; fi; \
echo openssl req -x509 -new $${ENG} -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -$${SUBJ##*-}; \
openssl req -x509 -new $${ENG} -key $< -out $@ -days 10000 -subj "/CN=roles-anywhere-$${SUBJ}" -$${SUBJ##*-};
endef

%-md5-cert.pem: %-key.pem; $(CERT_RECIPE)
%-sha256-cert.pem: %-key.pem; $(CERT_RECIPE)
%-sha1-cert.pem: %-key.pem; $(CERT_RECIPE)
%-sha384-cert.pem: %-key.pem; $(CERT_RECIPE)
%-sha512-cert.pem: %-key.pem; $(CERT_RECIPE)

%-combo.pem: %-cert.pem
KEY=$$(echo "$@" | sed 's/-[^-]*-combo.pem/-key.pem/'); \
cat $${KEY} $< > $@.tmp && mv $@.tmp $@

# Go PKCS#12 only supports SHA1 and 3DES!!
%.p12: %-pass.p12
echo Creating $@...
ls -l $<
%.p12: %-cert.pem
KEY=$$(echo "$@" | sed 's/-[^-]*\.p12/-key.pem/'); \
CERT=$$(echo "$@" | sed 's/.p12/-cert.pem/'); \
openssl pkcs12 -export -passout pass: -macalg SHA1 \
Expand All @@ -105,6 +187,15 @@ test: test-certs tst/softhsm2.conf
%-pkcs8.pem: %.pem
openssl pkcs8 -topk8 -inform PEM -outform PEM -in $< -out $@ -nocrypt

$(certsdir)/tpm-hw-rsa-key.pem:
create_tpm2_key -r $@

$(certsdir)/tpm-hw-ec-key.pem:
create_tpm2_key -e prime256v1 $@

$(certsdir)/tpm-hw-ec-81000001-key.pem:
create_tpm2_key -e prime256v1 -p 81000001 $@

$(RSAKEYS):
KEYLEN=$$(echo "$@" | sed 's/.*rsa-\([0-9]*\)-key.pem/\1/'); \
openssl genrsa -out $@ $${KEYLEN}
Expand All @@ -122,16 +213,27 @@ $(certsdir)/cert-bundle-with-comments.pem: $(RSACERTS) $(ECCERTS)
echo "Comment in bundle\n" >> $@; \
done

KEYS := $(RSAKEYS) $(ECKEYS) $(TPMKEYS) $(PKCS8KEYS)
CERTS := $(RSACERTS) $(ECCERTS) $(TPMCERTS)
COMBOS := $(patsubst %-cert.pem, %-combo.pem, $(CERTS))

test-certs: $(KEYS) $(CERTS) $(COMBOS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem tst/softhsm2.conf
$(STOP_SWTPM_TCP) 2>/dev/null || :

.PHONY: test-certs
test-certs: $(PKCS8KEYS) $(RSAKEYS) $(ECKEYS) $(RSACERTS) $(ECCERTS) $(PKCS12CERTS) $(certsdir)/cert-bundle.pem $(certsdir)/cert-bundle-with-comments.pem tst/softhsm2.conf

.PHONY: test-clean
test-clean:
rm -f $(RSAKEYS) $(ECKEYS)
rm -f $(RSAKEYS) $(ECKEYS) $(HWTPMKEYS)
rm -f $(PKCS8KEYS)
rm -f $(RSACERTS) $(ECCERTS)
rm -f $(PKCS12CERTS)
rm -f $(RSACERTS) $(ECCERTS) $(HWTPMCERTS)
rm -f $(PKCS12CERTS) $(COMBOS)
rm -f $(certsdir)/cert-bundle.pem
rm -f $(certsdir)/cert-with-comments.pem
rm -f tst/softhsm2.conf
rm -rf tst/softhsm/*
$(STOP_SWTPM_TCP) || :
$(STOP_SWTPM_UNIX) || :
rm -rf $(SWTPMKEYS) $(SWTPMCERTS) tst/swtpm

17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ to guarantee that PINs are zeroized in memory after they are no longer needed. W
to explore options to overcome this. Customers are encouraged to study the impact of this limitation
and determine whether compensating controls are warranted for their system and threat model.

#### TPMv2 Integration

Private key files containing a TPM wrapped key in the `-----BEGIN TSS2 PRIVATE KEY-----`
form as described [here](https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html)
are transparently supported. You can just use such a file as you would any plain key
file and expect it to work, just as you should expect with any well-behaved application.

These files are supported, and can be created by, both TPMv2 OpenSSL engines/providers, and GnuTLS.

Note that some features of the TSS private key format are not yet supported. Some or all
of these may be implemented in future versions. In some semblance of the order in which
they're likely to be added:
* Password authentication on keys (and parent keys)
* Importable keys
* TPM Policy / AuthPolicy
* Sealed keys

### update

Updates temporary credentials in the [credential file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). Parameters for this command include those for the `credential-process` command, as well as `--profile`, which specifies the named profile for which credentials should be updated (if the profile doesn't already exist, it will be created), and `--once`, which specifies that credentials should be updated only once. Both arguments are optional. If `--profile` isn't specified, the default profile will have its credentials updated, and if `--once` isn't specified, credentials will be continuously updated. In this case, credentials will be updated through a call to `CreateSession` five minutes before the previous set of credentials are set to expire. Please note that running the `update` command multiple times, creating multiple processes, may not work as intended. There may be issues with concurrent writes to the credentials file.
Expand Down
5 changes: 5 additions & 0 deletions aws_signing_helper/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ func GetSigner(opts *CredentialsOpts) (signer Signer, signatureAlgorithm string,
}
return GetPKCS11Signer(opts.LibPkcs11, certificate, certificateChain, opts.PrivateKeyId, opts.CertificateId, opts.ReusePin)
} else {
tpmkey, err := parseDERFromPEM(privateKeyId, "TSS2 PRIVATE KEY")
if err == nil {
return GetTPMv2Signer(certificate, certificateChain, tpmkey)
}

_, err = ReadPrivateKeyData(privateKeyId)
if err != nil {
return nil, "", err
Expand Down
59 changes: 56 additions & 3 deletions aws_signing_helper/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ func TestSign(t *testing.T) {
CertificateId: cert,
PrivateKeyId: key,
})

cert = fmt.Sprintf("../tst/certs/ec-%s-%s.p12",
curve, digest)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
})

cert = fmt.Sprintf("../tst/certs/ec-%s-%s-combo.pem",
curve, digest)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
})
}
}

Expand All @@ -276,6 +288,46 @@ func TestSign(t *testing.T) {
CertificateId: cert,
PrivateKeyId: key,
})

cert = fmt.Sprintf("../tst/certs/rsa-%s-%s.p12",
keylen, digest)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
})

cert = fmt.Sprintf("../tst/certs/rsa-%s-%s-combo.pem",
keylen, digest)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
})
}
}

tpm_digests := []string{"sha1", "sha256", "sha384", "sha512"}
var tpm_keys []string

tpmdev := os.Getenv("TPM_DEVICE")
if strings.HasPrefix(tpmdev, "/dev/") {
tpm_keys = []string{"hw-rsa", "hw-ec", "hw-ec-81000001"}
} else {
tpm_keys = []string{"sw-rsa", "sw-ec-prime256", "sw-ec-secp384r1", "sw-ec-81000001"}
}

for _, digest := range tpm_digests {
for _, keyname := range tpm_keys {
cert := fmt.Sprintf("../tst/certs/tpm-%s-%s-cert.pem",
keyname, digest)
key := fmt.Sprintf("../tst/certs/tpm-%s-key.pem", keyname)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
PrivateKeyId: key,
})

cert = fmt.Sprintf("../tst/certs/tpm-%s-%s-combo.pem",
keyname, digest)
testTable = append(testTable, CredentialsOpts{
CertificateId: cert,
})
}
}

Expand Down Expand Up @@ -355,7 +407,8 @@ func TestSign(t *testing.T) {
// makes sure that the context-specific PIN was saved.
signer.Sign(rand.Reader, []byte(msg), digest)
if err != nil {
t.Log("Failed to sign the input message")
t.Log(fmt.Sprintf("Failed to %s sign the input message for '%s'/'%s': %s",
digest, credOpts.CertificateId, credOpts.PrivateKeyId, err))
t.Fail()
return
}
Expand All @@ -369,8 +422,8 @@ func TestSign(t *testing.T) {
if pubKey != nil {
valid, _ := Verify([]byte(msg), pubKey, digest, signatureBytes)
if !valid {
t.Log(fmt.Sprintf("Failed to verify the signature for '%s'/'%s'",
credOpts.CertificateId, credOpts.PrivateKeyId))
t.Log(fmt.Sprintf("Failed to verify %s signature for '%s'/'%s'",
digest, credOpts.CertificateId, credOpts.PrivateKeyId))
t.Fail()
return
}
Expand Down
Loading