├── VERSION ├── .env ├── docs ├── TPM-Session.md ├── assets │ ├── tpm-provisioned.drawio.png │ └── reference-architecture.drawio.png ├── BUILD.md ├── TPM-FIPS.md ├── TPM-Encryption.md ├── NIST.md ├── CA-ASN1.md ├── ARCHITECTURE-REF.md ├── TPM-Attestation.md ├── man │ ├── verifier.md │ ├── attestor.md │ ├── ca │ │ ├── show.md │ │ ├── info.md │ │ ├── init.md │ │ ├── issue.md │ │ ├── uninstall-ca-certificates.md │ │ ├── revoke.md │ │ └── install-ca-certificates.md │ ├── tpm │ │ ├── info.md │ │ ├── ek.md │ │ └── seal.md │ ├── webservice.md │ ├── platform │ │ ├── destroy.md │ │ ├── password.md │ │ └── install.md │ ├── common │ │ └── options.md │ └── pkcs11.md ├── TPM-Provisioning.md ├── AUTHENTICATION.md └── TPM-PCRs.md ├── CHANGELOG ├── pkg ├── webservice │ ├── v1 │ │ ├── response │ │ │ └── types.go │ │ ├── router │ │ │ ├── types.go │ │ │ └── authentication.go │ │ ├── middleware │ │ │ └── types.go │ │ ├── types.go │ │ ├── keycache.go │ │ ├── jwt │ │ │ └── service_test.go │ │ └── test │ │ │ └── handler_key_change_test.go │ ├── loadbalancer.go │ ├── util.go │ ├── mtls_signer.go │ └── mtls_client.go ├── util │ ├── id.go │ ├── serialnumber.go │ ├── fs.go │ ├── fs_test.go │ └── leakybucket.go ├── acme │ ├── entities │ │ ├── identifier.go │ │ ├── nonce.go │ │ ├── certificate.go │ │ ├── challenge.go │ │ ├── authorization.go │ │ ├── account.go │ │ ├── errors.go │ │ └── order.go │ ├── server │ │ ├── types.go │ │ └── handlers │ │ │ ├── new_nonce.go │ │ │ ├── directory.go │ │ │ ├── certificate.go │ │ │ ├── handler_order.go │ │ │ ├── ca_bundle.go │ │ │ └── account.go │ ├── datastore.go │ ├── dao │ │ ├── afero │ │ │ ├── nonce.go │ │ │ ├── certificate.go │ │ │ ├── challenge.go │ │ │ ├── order.go │ │ │ ├── factory_test.go │ │ │ └── account.go │ │ └── types.go │ ├── challenge │ │ └── endorse01 │ │ │ └── endorse01.go │ └── datastore_test.go ├── main.go ├── store │ ├── keystore │ │ ├── pkcs8 │ │ │ └── config.go │ │ ├── decrypter_opts.go │ │ ├── dilithium2 │ │ │ ├── types.go │ │ │ ├── dilithium2_test.go │ │ │ └── dilithium2.go │ │ ├── base_test.go │ │ ├── pkcs11 │ │ │ ├── testdata │ │ │ │ └── softhsm.conf │ │ │ └── types.go │ │ ├── parser_test.go │ │ ├── tpm2 │ │ │ ├── types.go │ │ │ ├── secret.go │ │ │ └── signer_ecdsa_test.go │ │ ├── password │ │ │ ├── required.go │ │ │ └── clear.go │ │ ├── shamir.go │ │ ├── shamir_test.go │ │ ├── signer_opts.go │ │ ├── password.go │ │ └── signer_store.go │ ├── datastore │ │ ├── entities │ │ │ ├── types.go │ │ │ ├── role.go │ │ │ ├── service.go │ │ │ ├── organization.go │ │ │ ├── registration.go │ │ │ ├── index_reference.go │ │ │ ├── blob.go │ │ │ └── aggregate_root.go │ │ ├── kvstore │ │ │ ├── user.go │ │ │ ├── webauthn.go │ │ │ ├── registration.go │ │ │ └── role.go │ │ └── serializer_test.go │ ├── certstore │ │ ├── backend_blobstore.go │ │ ├── base_test.go │ │ ├── backend_test.go │ │ └── encoding.go │ └── blob │ │ └── file_test.go ├── platform │ ├── service │ │ ├── service.go │ │ ├── role.go │ │ ├── user.go │ │ ├── registration.go │ │ ├── types.go │ │ └── session_webauthn.go │ ├── auth │ │ ├── types.go │ │ └── yubikey.go │ └── prompt │ │ └── prompt.go ├── cmd │ ├── tpm │ │ ├── types.go │ │ ├── base_test.go │ │ ├── info.go │ │ ├── info_test.go │ │ ├── provision_test.go │ │ ├── eventlog.go │ │ └── provision.go │ ├── dns.go │ ├── platform │ │ ├── types.go │ │ ├── base_test.go │ │ ├── install_test.go │ │ ├── policy_test.go │ │ ├── provision_test.go │ │ ├── policy.go │ │ └── install.go │ ├── ca │ │ ├── types.go │ │ ├── base_test.go │ │ ├── init_test.go │ │ ├── install.go │ │ ├── uninstall.go │ │ ├── init.go │ │ ├── info_test.go │ │ ├── revoke.go │ │ └── info.go │ ├── system.go │ ├── version.go │ ├── platform.go │ └── status.go ├── dns │ ├── dao │ │ ├── types.go │ │ └── afero │ │ │ └── zone.go │ ├── datastore.go │ ├── tld_test.go │ └── tld.go ├── device │ ├── dao │ │ ├── types.go │ │ └── afero │ │ │ ├── device.go │ │ │ └── profile.go │ ├── entities │ │ ├── profile.go │ │ └── device.go │ ├── config.go │ ├── datastore.go │ └── service.go ├── ca │ ├── quantum_unsafe.go │ ├── quantum_safe.go │ ├── types.go │ ├── encoding.go │ └── trust_store_debian.go ├── logging │ ├── logger_test.go │ └── error.go ├── crypto │ ├── argon2 │ │ ├── types.go │ │ └── argon2id_test.go │ └── aesgcm │ │ └── aesgcm_test.go ├── serializer │ ├── serializer_json.go │ └── serializer_yaml.go ├── config │ └── attestation.go ├── app │ └── version.go └── tpm2 │ ├── random_test.go │ ├── encoding_test.go │ ├── provision_test.go │ ├── tpm_test.go │ ├── version.go │ ├── encoding.go │ └── secretkey.go ├── examples ├── tss │ ├── attestor │ │ ├── pkg │ │ │ ├── util.go │ │ │ ├── stats.go │ │ │ └── proto │ │ │ │ └── attestation.proto │ │ └── .vscode │ │ │ ├── tasks.json │ │ │ └── launch.json │ └── verifier │ │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ │ └── pkg │ │ └── proto │ │ └── attestation.proto ├── client │ └── Makefile └── server │ └── Makefile ├── .vscode ├── settings.json └── tasks.json ├── configs ├── os │ └── trusted-platform.service ├── softhsm.conf └── firefox │ └── policies.json └── .gitignore /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.6-alpha.1 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | export TPM2_PKCS11_STORE=/tmp 2 | alias tpm2pkcs11-tool="pkcs11-tool --module /usr/local/lib/libtpm2_pkcs11.so" -------------------------------------------------------------------------------- /docs/TPM-Session.md: -------------------------------------------------------------------------------- 1 | # TPM Session and Authorization 2 | 3 | https://link.springer.com/chapter/10.1007/978-1-4302-6584-9_13 4 | -------------------------------------------------------------------------------- /docs/assets/tpm-provisioned.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyhahn/go-trusted-platform/HEAD/docs/assets/tpm-provisioned.drawio.png -------------------------------------------------------------------------------- /docs/BUILD.md: -------------------------------------------------------------------------------- 1 | # Build Docs 2 | 3 | # LinuxKit 4 | 5 | https://medium.com/aishik/getting-started-with-linuxkit-and-moby-project-ff7121c4e321 6 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | - Include cross-signing CA's root and intermediate 2 | certs in TLS configs, cert bundles and cert pools. 3 | - Misc bug fixes and cleanup -------------------------------------------------------------------------------- /docs/assets/reference-architecture.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremyhahn/go-trusted-platform/HEAD/docs/assets/reference-architecture.drawio.png -------------------------------------------------------------------------------- /pkg/webservice/v1/response/types.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrServiceNotFound = errors.New("service not found") 7 | ) 8 | -------------------------------------------------------------------------------- /docs/TPM-FIPS.md: -------------------------------------------------------------------------------- 1 | # TPM FIPS 2 | 3 | https://trustedcomputinggroup.org/wp-content/uploads/TCG-FIPS-140-3-Guidance-for-TPM-2.0-Version-1.0-Revision-1_14Feb24.pdf 4 | 5 | -------------------------------------------------------------------------------- /pkg/util/id.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/cespare/xxhash/v2" 5 | ) 6 | 7 | func NewID(input []byte) uint64 { 8 | return xxhash.Sum64(input) 9 | } 10 | -------------------------------------------------------------------------------- /docs/TPM-Encryption.md: -------------------------------------------------------------------------------- 1 | # TPM Encryption 2 | 3 | https://trustedcomputinggroup.org/wp-content/uploads/TCG_-CPU_-TPM_Bus_Protection_Guidance_Active_Attack_Mitigations-V1-R30_PUB-1.pdf 4 | -------------------------------------------------------------------------------- /pkg/acme/entities/identifier.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type ACMEIdentifier struct { 4 | Type string `yaml:"type" json:"type"` 5 | Value string `yaml:"value" json:"value"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package main 4 | 5 | import "github.com/jeremyhahn/go-trusted-platform/pkg/cmd" 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/webservice/v1/router/types.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | ) 6 | 7 | type WebServiceRouter interface { 8 | RegisterRoutes(router *mux.Router) 9 | } 10 | -------------------------------------------------------------------------------- /examples/tss/attestor/pkg/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | func parseVerifierIP(addr net.Addr) string { 9 | pieces := strings.Split(addr.String(), ":") 10 | return pieces[0] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "makefile.configureOnOpen": false, 3 | "go.buildFlags": [ 4 | <<<<<<< HEAD 5 | "-tags", "quantum_safe" 6 | ======= 7 | "-tags", "quantum_safe", 8 | >>>>>>> feature/acme 9 | ] 10 | } -------------------------------------------------------------------------------- /pkg/store/keystore/pkcs8/config.go: -------------------------------------------------------------------------------- 1 | package pkcs8 2 | 3 | type Config struct { 4 | CN string `yaml:"cn" json:"cn" mapstructure:"cn"` 5 | PlatformPolicy bool `yaml:"platform-policy" json:"platform_policy" mapstructure:"platform-policy"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/platform/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 4 | 5 | type Service struct { 6 | ID uint64 7 | Name string 8 | KeyAttributes *keystore.KeyAttributes 9 | } 10 | -------------------------------------------------------------------------------- /pkg/util/serialnumber.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | ) 7 | 8 | func SerialNumber() (*big.Int, error) { 9 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 10 | return rand.Int(rand.Reader, serialNumberLimit) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/types.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | ) 6 | 7 | var ( 8 | App *app.App 9 | InitParams *app.AppInitParams 10 | err error 11 | ) 12 | 13 | func init() { 14 | InitParams = &app.AppInitParams{} 15 | } 16 | -------------------------------------------------------------------------------- /docs/NIST.md: -------------------------------------------------------------------------------- 1 | # National Institute of Standards and Technology 2 | 3 | 4 | 1. [BIOS Protection Guidelines](https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-147.pdf) 5 | 6 | 2. [BIOS Protection Guidelines for Servers](https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-147b.pdf) 7 | 8 | -------------------------------------------------------------------------------- /docs/CA-ASN1.md: -------------------------------------------------------------------------------- 1 | # Distinguished Encoding Rules (DER) form 2 | 3 | ### A Warm Welcome to ASN.1 and DER 4 | 5 | ASN.1’s main serialization format is “Distinguished Encoding Rules” (DER). They are a variant of “Basic Encoding Rules” (BER) with canonicalization added. 6 | 7 | https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ 8 | 9 | -------------------------------------------------------------------------------- /pkg/platform/auth/types.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrAuthenticationFailed = errors.New("platform: authentication failed") 7 | ) 8 | 9 | type PlatformAuthenticator interface { 10 | Authenticate(password []byte) error 11 | Prompt() []byte 12 | Provision(cn string, password, caPassword []byte) error 13 | } 14 | -------------------------------------------------------------------------------- /pkg/store/keystore/decrypter_opts.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | type DecrypterOpts struct { 4 | EncryptAttributes *KeyAttributes 5 | BlobCN *string 6 | BlobData []byte 7 | StoreEncryptedBlob bool 8 | Label []byte 9 | } 10 | 11 | func NewDecrypterOpts() DecrypterOpts { 12 | return DecrypterOpts{} 13 | } 14 | -------------------------------------------------------------------------------- /pkg/cmd/dns.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCmd.AddCommand(dnsCmd) 9 | } 10 | 11 | var dnsCmd = &cobra.Command{ 12 | Use: "dns", 13 | Short: "DNS server", 14 | Long: `Performs Domain Name Service operations`, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /pkg/acme/server/types.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "github.com/go-jose/go-jose/v4" 4 | 5 | var ( 6 | AllowedJOSEAlgorithms = []jose.SignatureAlgorithm{ 7 | jose.RS256, 8 | jose.RS384, 9 | jose.RS512, 10 | jose.PS256, 11 | jose.PS384, 12 | jose.PS512, 13 | jose.ES256, 14 | jose.ES384, 15 | jose.ES512, 16 | jose.EdDSA, 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/dns/dao/types.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/entities" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 6 | ) 7 | 8 | type ZoneDAO interface { 9 | datastore.GenericDAO[*entities.Zone] 10 | GetByName(name string, consistencyLevel datastore.ConsistencyLevel) (*entities.Zone, error) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/device/dao/types.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/entities" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 6 | ) 7 | 8 | type DeviceProfileDAO interface { 9 | datastore.GenericDAO[*entities.DeviceProfile] 10 | } 11 | 12 | type DeviceDAO interface { 13 | datastore.GenericDAO[*entities.Device] 14 | } 15 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/types.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type KeyValueEntity interface { 4 | SetEntityID(id uint64) 5 | EntityID() uint64 6 | } 7 | 8 | type Index interface { 9 | Name() string 10 | RefID() uint64 11 | KeyValueEntity 12 | } 13 | 14 | type TimeSeriesIndexer interface { 15 | KeyValueEntity 16 | SetTimestamp(timestamp uint64) 17 | Timestamp() uint64 18 | } 19 | -------------------------------------------------------------------------------- /pkg/device/entities/profile.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type DeviceProfile struct { 4 | ID uint64 `yaml:"id" json:"id"` 5 | Model string `yaml:"model" json:"model"` 6 | EventLog []byte `yaml:"eventLog" json:"eventLog"` 7 | } 8 | 9 | func (dp *DeviceProfile) EntityID() uint64 { 10 | return dp.ID 11 | } 12 | 13 | func (dp *DeviceProfile) SetEntityID(id uint64) { 14 | dp.ID = id 15 | } 16 | -------------------------------------------------------------------------------- /pkg/store/keystore/dilithium2/types.go: -------------------------------------------------------------------------------- 1 | //go:build quantum_safe 2 | 3 | package dilithium2 4 | 5 | import "crypto/x509" 6 | 7 | type Dilithium2KeyAlgorithm x509.PublicKeyAlgorithm 8 | 9 | func (pka Dilithium2KeyAlgorithm) String() string { 10 | return "Dilithium2" 11 | } 12 | 13 | type Dilithium2SignatureAlgorithm int 14 | 15 | func (pka Dilithium2SignatureAlgorithm) String() string { 16 | return "Dilithium2" 17 | } 18 | -------------------------------------------------------------------------------- /pkg/webservice/loadbalancer.go: -------------------------------------------------------------------------------- 1 | package webservice 2 | 3 | import "sync" 4 | 5 | type LoadBalancerFunc func(backends []string) string 6 | 7 | func RoundRobinBalancer() LoadBalancerFunc { 8 | var mu sync.Mutex 9 | var idx int 10 | return func(backends []string) string { 11 | mu.Lock() 12 | defer mu.Unlock() 13 | selected := backends[idx] 14 | idx = (idx + 1) % len(backends) 15 | return selected 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/ca/quantum_unsafe.go: -------------------------------------------------------------------------------- 1 | //go:build !quantum_safe 2 | 3 | package ca 4 | 5 | import ( 6 | "crypto/x509/pkix" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 9 | ) 10 | 11 | // Returns an empty set of quantum signature extensions for installations 12 | // that choose to opt-out of quantum-safe algorithms. 13 | func quantumSafeExtentions(algorithm keystore.QuantumAlgorithm, data []byte) []pkix.Extension { 14 | return []pkix.Extension{} 15 | } 16 | -------------------------------------------------------------------------------- /pkg/store/keystore/base_test.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | TEST_DATA_DIR = "./testdata" 10 | TEST_CN = "exapmle.com" 11 | TEST_TMP_DIR = "" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | setup() 16 | code := m.Run() 17 | teardown() 18 | os.Exit(code) 19 | } 20 | 21 | func teardown() { 22 | // os.RemoveAll(TEST_DATA_DIR) 23 | } 24 | 25 | func setup() { 26 | os.RemoveAll(TEST_DATA_DIR) 27 | } 28 | -------------------------------------------------------------------------------- /configs/os/trusted-platform.service: -------------------------------------------------------------------------------- 1 | # /lib/systemd/system/trusted-platform.service 2 | [Unit] 3 | Description=Trusted Platform 4 | 5 | [Service] 6 | WorkingDirectory=/opt/trusted-platform/ 7 | Type=simple 8 | Restart=always 9 | RestartSec=5s 10 | ExecStart=/usr/local/bin/tpadm webservice --debug --platform-dir /opt/trusted-platform/trusted-data --log-dir /opt/trusted-platform/trusted-data/log --ca-dir /opt/trusted-data/trusted-data/ca 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /pkg/cmd/platform/types.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/ca" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 7 | ) 8 | 9 | var ( 10 | App *app.App 11 | TPM tpm2.TrustedPlatformModule 12 | CAParams *ca.CAParams 13 | InitParams *app.AppInitParams 14 | err error 15 | ) 16 | 17 | func init() { 18 | InitParams = &app.AppInitParams{} 19 | } 20 | -------------------------------------------------------------------------------- /pkg/cmd/ca/types.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/ca" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 7 | ) 8 | 9 | var ( 10 | App *app.App 11 | TPM tpm2.TrustedPlatformModule 12 | CAParams *ca.CAParams 13 | InitParams *app.AppInitParams 14 | SansDNS, 15 | SansIPs, 16 | SansEmails string 17 | err error 18 | ) 19 | 20 | func init() { 21 | InitParams = &app.AppInitParams{} 22 | } 23 | -------------------------------------------------------------------------------- /pkg/store/keystore/pkcs11/testdata/softhsm.conf: -------------------------------------------------------------------------------- 1 | 2 | # SoftHSM v2 configuration file 3 | 4 | directories.tokendir = testdata/ 5 | objectstore.backend = file 6 | objectstore.umask = 0077 7 | 8 | # ERROR, WARNING, INFO, DEBUG 9 | log.level = DEBUG 10 | 11 | # If CKF_REMOVABLE_DEVICE flag should be set 12 | slots.removable = false 13 | 14 | # Enable and disable PKCS#11 mechanisms using slots.mechanisms. 15 | slots.mechanisms = ALL 16 | 17 | # If the library should reset the state on fork 18 | library.reset_on_fork = false 19 | -------------------------------------------------------------------------------- /pkg/cmd/ca/base_test.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log/slog" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func executeCommand(cmd *cobra.Command, args []string) string { 12 | 13 | b := new(bytes.Buffer) 14 | 15 | cmd.SetOut(b) 16 | cmd.SetErr(b) 17 | cmd.SetArgs(args) 18 | 19 | err := cmd.Execute() 20 | if err != nil { 21 | slog.Error(err.Error()) 22 | return err.Error() 23 | } 24 | 25 | response := string(b.Bytes()) 26 | fmt.Println(response) 27 | 28 | return response 29 | } 30 | -------------------------------------------------------------------------------- /pkg/store/keystore/parser_test.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "crypto" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParseHash(t *testing.T) { 11 | hashes := AvailableHashes() 12 | hash, ok := hashes["SHA-256"] 13 | assert.True(t, ok) 14 | assert.Equal(t, crypto.SHA256, hash) 15 | } 16 | 17 | func TestParseBadHash(t *testing.T) { 18 | hashes := AvailableHashes() 19 | hash, ok := hashes["SHA256"] 20 | assert.False(t, ok) 21 | assert.Equal(t, crypto.Hash(0), hash) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/base_test.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log/slog" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func executeCommand(cmd *cobra.Command, args []string) string { 12 | 13 | b := new(bytes.Buffer) 14 | 15 | cmd.SetOut(b) 16 | cmd.SetErr(b) 17 | cmd.SetArgs(args) 18 | 19 | err := cmd.Execute() 20 | if err != nil { 21 | slog.Error(err.Error()) 22 | return err.Error() 23 | } 24 | 25 | response := string(b.Bytes()) 26 | fmt.Println(response) 27 | 28 | return response 29 | } 30 | -------------------------------------------------------------------------------- /pkg/cmd/platform/base_test.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log/slog" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func executeCommand(cmd *cobra.Command, args []string) string { 12 | 13 | b := new(bytes.Buffer) 14 | 15 | cmd.SetOut(b) 16 | cmd.SetErr(b) 17 | cmd.SetArgs(args) 18 | 19 | err := cmd.Execute() 20 | if err != nil { 21 | slog.Error(err.Error()) 22 | return err.Error() 23 | } 24 | 25 | response := string(b.Bytes()) 26 | fmt.Println(response) 27 | 28 | return response 29 | } 30 | -------------------------------------------------------------------------------- /configs/softhsm.conf: -------------------------------------------------------------------------------- 1 | # SoftHSM v2 configuration file 2 | 3 | # directories.tokendir = /var/lib/softhsm/tokens/ 4 | directories.tokendir = trusted-data/softhsm2 5 | objectstore.backend = file 6 | objectstore.umask = 0077 7 | 8 | # ERROR, WARNING, INFO, DEBUG 9 | log.level = DEBUG 10 | 11 | # If CKF_REMOVABLE_DEVICE flag should be set 12 | slots.removable = false 13 | 14 | # Enable and disable PKCS#11 mechanisms using slots.mechanisms. 15 | slots.mechanisms = ALL 16 | 17 | # If the library should reset the state on fork 18 | library.reset_on_fork = false 19 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/role.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/util" 4 | 5 | type Role struct { 6 | ID uint64 `json:"id"` 7 | Name string `json:"name"` 8 | KeyValueEntity `yaml:"-" json:"-"` 9 | } 10 | 11 | func NewRole(name string) *Role { 12 | return &Role{ 13 | ID: util.NewID([]byte(name)), 14 | Name: name, 15 | } 16 | } 17 | 18 | func (role *Role) SetEntityID(id uint64) { 19 | role.ID = id 20 | } 21 | 22 | func (role *Role) EntityID() uint64 { 23 | return role.ID 24 | } 25 | -------------------------------------------------------------------------------- /pkg/acme/entities/nonce.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 4 | 5 | type ACMENonce struct { 6 | ID uint64 `json:"id" yaml:"id"` 7 | Value []byte `json:"value" yaml:"value"` 8 | ExpiresAt int64 `json:"expires" yaml:"expires"` 9 | entities.KeyValueEntity `json:"-" yaml:"-"` 10 | } 11 | 12 | func (nonce *ACMENonce) SetEntityID(id uint64) { 13 | nonce.ID = id 14 | } 15 | 16 | func (nonce *ACMENonce) EntityID() uint64 { 17 | return nonce.ID 18 | } 19 | -------------------------------------------------------------------------------- /pkg/store/keystore/tpm2/types.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 6 | ) 7 | 8 | type PlatformKeyStorer interface { 9 | CreatePassword(keyAttrs *keystore.KeyAttributes, backend keystore.KeyBackend, overwrite bool) error 10 | KeyAttributes() *keystore.KeyAttributes 11 | Password(keyAttrs *keystore.KeyAttributes) (keystore.Password, error) 12 | SRKAttributes() *keystore.KeyAttributes 13 | TPM2() tpm2.TrustedPlatformModule 14 | keystore.KeyStorer 15 | } 16 | -------------------------------------------------------------------------------- /pkg/webservice/v1/middleware/types.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/service" 7 | ) 8 | 9 | type JsonWebTokenMiddleware interface { 10 | CreateSession(w http.ResponseWriter, r *http.Request) (service.Session, error) 11 | GenerateToken(w http.ResponseWriter, req *http.Request) 12 | AuthMiddleware 13 | } 14 | 15 | type AuthMiddleware interface { 16 | RefreshToken(w http.ResponseWriter, req *http.Request) 17 | Verify(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "debug_client_config_clean", 7 | "command": "rm -rf pkg/trusted-data && mkdir -p pkg/trusted-data/etc && cp configs/platform/config.dev.client.yaml pkg/trusted-data/etc/config.yaml", 8 | }, 9 | { 10 | "type": "shell", 11 | "label": "debug_server_config_clean", 12 | "command": "rm -rf pkg/trusted-data && mkdir -p pkg/trusted-data/etc && cp configs/platform/config.dev.server.yaml pkg/trusted-data/etc/config.yaml", 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /pkg/cmd/ca/init_test.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_Init(t *testing.T) { 11 | 12 | InitParams.Initialize = true 13 | InitParams.Pin = []byte("test") 14 | InitParams.SOPin = []byte("test") 15 | InitParams.Env = app.EnvTest.String() 16 | 17 | App = app.DefaultTestConfig() 18 | 19 | response := executeCommand(InitCmd, []string{}) 20 | assert.Equal(t, "Certificate Authority successfully initialized\n", response) 21 | 22 | App.TPM.Close() 23 | } 24 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE-REF.md: -------------------------------------------------------------------------------- 1 | # Reference Architecture 2 | 3 | This document discusses the reference architecture for the Trusted Platform. 4 | 5 | There are many different ways to deploy and use the Trusted Platform, for example, home, office or corporate authentication and network access, devops automation workflows, building secure, trusted web services, and running a public or private cloud. 6 | 7 | Here we will take a look at an Internet of Things (IoT) platform that operates as a hybrid cloud. 8 | 9 | 10 | ![Alt text](assets/reference-architecture.drawio.png?raw=true "IoT Reference Architecture") 11 | -------------------------------------------------------------------------------- /pkg/webservice/v1/types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | var ( 10 | ErrInvalidToken = errors.New("invalid token") 11 | ErrInvalidUserClaim = errors.New("invalid user id claim") 12 | ErrInvalidEmailClaim = errors.New("invalid email claim") 13 | ) 14 | 15 | type RestService interface { 16 | RegisterEndpoints(router *mux.Router, baseURI, baseFarmURI string) []string 17 | } 18 | 19 | // type GenericRestService[E any] struct { 20 | // logger *logging.Logger 21 | // jsonWriter response.HttpWriter 22 | // SystemRestHandler 23 | // } 24 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/info.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var InfoCmd = &cobra.Command{ 8 | Use: "info", 9 | Short: "Retrieve TPM 2.0 general information", 10 | Long: `Display TPM 2.0 Endorsement Public Key in PEM form`, 11 | Run: func(cmd *cobra.Command, args []string) { 12 | 13 | if _, err := App.Init(InitParams); err != nil { 14 | App.Logger.Error(err) 15 | cmd.PrintErrln(err) 16 | return 17 | } 18 | 19 | info, err := App.TPM.Info() 20 | if err != nil { 21 | cmd.PrintErrln(err) 22 | return 23 | } 24 | cmd.Println(info) 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /pkg/acme/entities/certificate.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type ACMECertificate struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | CertURL string `yaml:"cert-url" json:"cert_url"` 8 | PEM string `yaml:"pem" json:"pem"` 9 | IssuedAt time.Time `yaml:"issued" json:"issued"` 10 | Status string `yaml:"status" json:"status"` 11 | ExpiresAt time.Time `yaml:"expires" json:"expires"` 12 | } 13 | 14 | func (cert *ACMECertificate) SetEntityID(id uint64) { 15 | cert.ID = id 16 | } 17 | 18 | func (cert *ACMECertificate) EntityID() uint64 { 19 | return cert.ID 20 | } 21 | -------------------------------------------------------------------------------- /pkg/cmd/system.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/system" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func init() { 10 | 11 | rootCmd.AddCommand(systemInfoCmd) 12 | } 13 | 14 | var systemInfoCmd = &cobra.Command{ 15 | Use: "info", 16 | Short: "Displays system information", 17 | Long: `Displays information about the platform hardware`, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | if err := system.PrintSystemInfo(); err != nil { 20 | color.New(color.FgRed).Println(err) 21 | return 22 | } 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /pkg/logging/logger_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "errors" 5 | "log/slog" 6 | "testing" 7 | ) 8 | 9 | func TestLogger(t *testing.T) { 10 | 11 | logger := NewLogger(slog.LevelDebug, nil) 12 | 13 | logger.Info("info test") 14 | logger.Warn("warn test") 15 | // logger.Error("error test") 16 | logger.Debug("debug test") 17 | } 18 | 19 | func TestError(t *testing.T) { 20 | 21 | logger := NewLogger(slog.LevelDebug, nil) 22 | 23 | err := errors.New("an error occurred") 24 | 25 | logger.Info("info test") 26 | logger.Warn("warn test") 27 | logger.Error(err) 28 | logger.Debug("debug test") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/device/config.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 5 | ) 6 | 7 | var ( 8 | DatastorePartition = "devices" 9 | 10 | Configuration *Config 11 | 12 | DefaultConfig = Config{ 13 | Datastore: &datastore.Config{ 14 | Backend: "AFERO_FS", 15 | ConsistencyLevel: "local", 16 | RootDir: "trusted-data/datastore", 17 | ReadBufferSize: 50, 18 | Serializer: "json", 19 | }, 20 | } 21 | ) 22 | 23 | type Config struct { 24 | Datastore *datastore.Config `yaml:"datastore" json:"datastore" mapstructure:"datastore"` 25 | } 26 | -------------------------------------------------------------------------------- /docs/TPM-Attestation.md: -------------------------------------------------------------------------------- 1 | # TPM Attestation 2 | 3 | # Remote Attestation 4 | 5 | https://tpm2-software.github.io/tpm2-tss/getting-started/2019/12/18/Remote-Attestation.html 6 | 7 | https://tpm2-software.github.io/2020/06/12/Remote-Attestation-With-tpm2-tools.html 8 | 9 | 10 | ### TPM Remote Attestation protocol using go-tpm and gRPC 11 | 12 | https://github.com/salrashid123/go_tpm_remote_attestation 13 | 14 | ### TPM based TLS using Attested Keys 15 | 16 | https://github.com/salrashid123/tls_ak 17 | 18 | ### Issue CA-signed certificate using TPM public key 19 | 20 | https://gist.github.com/salrashid123/10320c153ad6acdc31854c9775c43c0d -------------------------------------------------------------------------------- /pkg/crypto/argon2/types.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | type Argon2Config struct { 4 | Memory uint32 `yaml:"memory" json:"memory" mapstructure:"memory"` 5 | Iterations uint32 `yaml:"iterations" json:"iterations" mapstructure:"iterations"` 6 | Parallelism uint8 `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` 7 | SaltLength uint32 `yaml:"saltLen" json:"saltLen" mapstructure:"saltLen"` 8 | KeyLength uint32 `yaml:"keyLen" json:"keyLen" mapstructure:"keyLen"` 9 | } 10 | 11 | type Argon2 interface { 12 | Hash(password string) (string, error) 13 | Compare(password, hash string) (match bool, err error) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/cmd/platform/install_test.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Install(t *testing.T) { 12 | 13 | app.DefaultConfig.TPMConfig.EK.CertHandle = 0 14 | 15 | InitParams.Pin = []byte("test") 16 | InitParams.SOPin = []byte("test") 17 | InitParams.Env = app.EnvTest.String() 18 | 19 | App = app.DefaultTestConfig() 20 | 21 | response := executeCommand(InstallCmd, []string{}) 22 | assert.True(t, strings.Contains(response, "-----BEGIN CERTIFICATE")) 23 | 24 | App.TPM.Close() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/info_test.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Info(t *testing.T) { 12 | 13 | InitParams.Initialize = true 14 | InitParams.Pin = []byte("test") 15 | InitParams.SOPin = []byte("test") 16 | InitParams.Env = app.EnvTest.String() 17 | 18 | App = app.DefaultTestConfig() 19 | 20 | response := executeCommand(InfoCmd, []string{}) 21 | assert.True(t, strings.Contains(response, "Microsoft")) 22 | assert.True(t, strings.Contains(response, "xCG fTPM")) 23 | 24 | App.TPM.Close() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/service.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/util" 6 | ) 7 | 8 | type Service struct { 9 | ID uint64 10 | Name string 11 | KeyAttributes *keystore.KeyAttributes 12 | } 13 | 14 | func NewService(name string) *Service { 15 | return &Service{ 16 | ID: util.NewID([]byte(name)), 17 | Name: name, 18 | } 19 | } 20 | 21 | func (service *Service) SetEntityID(id uint64) { 22 | service.ID = id 23 | } 24 | 25 | func (service *Service) EntityID() uint64 { 26 | return service.ID 27 | } 28 | -------------------------------------------------------------------------------- /pkg/store/keystore/password/required.go: -------------------------------------------------------------------------------- 1 | package password 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 4 | 5 | type RequiredPassword struct { 6 | keystore.Password 7 | } 8 | 9 | // Creates a secret that always returns ErrPasswordRequired 10 | func NewRequiredPassword() keystore.Password { 11 | return RequiredPassword{} 12 | } 13 | 14 | // Returns ErrPasswordRequired 15 | func (p RequiredPassword) String() (string, error) { 16 | return "", keystore.ErrPasswordRequired 17 | } 18 | 19 | // Returns ErrPasswordRequired 20 | func (p RequiredPassword) Bytes() ([]byte, error) { 21 | return nil, keystore.ErrPasswordRequired 22 | } 23 | -------------------------------------------------------------------------------- /pkg/store/keystore/shamir.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import "github.com/SSSaaS/sssa-golang" 4 | 5 | // Returns a secret split into shares using the Shamir Secret Sharing algorithm. 6 | // Required is the number of shares required to re-create the secret. Shares is 7 | // the number of shards the secret is split into. 8 | func ShareSecret(required int, secret []byte, shares int) ([]string, error) { 9 | return sssa.Create(required, shares, string(secret)) 10 | } 11 | 12 | // Returns secret combined from shares split using Shamir's Secret 13 | // Sharing algorithm 14 | func SecretFromShares(shares []string) (string, error) { 15 | return sssa.Combine(shares) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/organization.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/util" 4 | 5 | type Organization struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | Name string `yaml:"name" json:"name"` 8 | KeyValueEntity `yaml:"-" json:"-"` 9 | } 10 | 11 | func NewOrganization(name string) *Organization { 12 | return &Organization{ 13 | ID: util.NewID([]byte(name)), 14 | Name: name, 15 | } 16 | } 17 | 18 | func (organization *Organization) SetEntityID(id uint64) { 19 | organization.ID = id 20 | } 21 | 22 | func (organization *Organization) EntityID() uint64 { 23 | return organization.ID 24 | } 25 | -------------------------------------------------------------------------------- /pkg/webservice/v1/keycache.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "crypto" 5 | "sync" 6 | ) 7 | 8 | var cache = newKeyCache() 9 | 10 | type keyCache struct { 11 | keys map[string]crypto.PublicKey 12 | mutex sync.RWMutex 13 | } 14 | 15 | func newKeyCache() *keyCache { 16 | return &keyCache{ 17 | keys: make(map[string]crypto.PublicKey), 18 | } 19 | } 20 | 21 | func (cache *keyCache) put(id string, key crypto.PublicKey) { 22 | cache.mutex.Lock() 23 | defer cache.mutex.Unlock() 24 | cache.keys[id] = key 25 | } 26 | 27 | func (cache *keyCache) key(id string) crypto.PublicKey { 28 | cache.mutex.RLock() 29 | defer cache.mutex.RUnlock() 30 | return cache.keys[id] 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/provision_test.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Provision(t *testing.T) { 12 | 13 | InitParams.Pin = []byte("test") 14 | InitParams.SOPin = []byte("test") 15 | InitParams.Env = app.EnvTest.String() 16 | 17 | App = app.DefaultTestConfig() 18 | 19 | response := executeCommand(ProvisionCmd, []string{}) 20 | assert.True(t, strings.Contains(response, "ENDORSEMENT")) 21 | assert.True(t, strings.Contains(response, "STORAGE")) 22 | assert.True(t, strings.Contains(response, "ATTESTATION")) 23 | 24 | App.TPM.Close() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/store/datastore/kvstore/user.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 6 | ) 7 | 8 | const ( 9 | user_partition = "users" 10 | ) 11 | 12 | type UserDAO struct { 13 | *AferoDAO[*entities.User] 14 | } 15 | 16 | func NewUserDAO(params *datastore.Params[*entities.User]) (datastore.UserDAO, error) { 17 | if params.Partition == "" { 18 | params.Partition = user_partition 19 | } 20 | aferoDAO, err := NewAferoDAO[*entities.User](params) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &UserDAO{ 25 | AferoDAO: aferoDAO, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /trusted-platform 2 | **/*.pb.go 3 | **/*.log 4 | **/*.bin 5 | **/*.pub 6 | **/*.key 7 | **/*.pkcs1 8 | **/*.pkcs8 9 | **/*.cer 10 | **/*.crt 11 | **/*.sig 12 | **/*.crl 13 | **/*.luks* 14 | db/ 15 | logs/ 16 | ca/certs 17 | tpm2/certs 18 | attestation/attestor/trusted-data 19 | attestation/attestor/logs 20 | attestation/attestor/config.yaml 21 | attestation/verifier/trusted-data 22 | attestation/verifier/logs 23 | attestation/verifier/config.yaml 24 | python-venv/ 25 | tpadm 26 | ./config.yaml 27 | ./pkg/config.yaml 28 | pkg/public_html 29 | pkg/trusted-data 30 | trusted-data 31 | build/packer/output-arm-image/ 32 | build/packer/packer_cache/ 33 | build/packer/trusted-platform-* 34 | trusted-platform.iso 35 | -------------------------------------------------------------------------------- /docs/man/verifier.md: -------------------------------------------------------------------------------- 1 | % tpadm verifier | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **verifier** - Starts the Verifier gRPC client 6 | 7 | # SYNOPSIS 8 | 9 | **verifier** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **verifier** - Performs Remote Attestation with the specified Attestor. 14 | 15 | 16 | # OPTIONS 17 | 18 | * **-a**, **\--attestor**: 19 | 20 | The IP address, host name, or DNS name of the attestor gRPC service 21 | 22 | 23 | ## References 24 | 25 | [common options](common/options.md) collection of common options that provide 26 | information many users may expect. 27 | 28 | # NOTES 29 | 30 | 31 | # EXAMPLES 32 | 33 | ## Start the verifier service 34 | ```bash 35 | tpadm verifier 36 | ``` 37 | -------------------------------------------------------------------------------- /pkg/store/keystore/pkcs11/types.go: -------------------------------------------------------------------------------- 1 | package pkcs11 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrUnsupportedKeyAlgorithm = errors.New("keystore/pkcs11: unsupported key algorithm") 7 | ErrUnsupportedOperation = errors.New("keystore/pkcs11: unsupported operation") 8 | ErrInvalidSOPIN = errors.New("keystore/pkcs11: invalid security officer pin") 9 | ErrInvalidUserPIN = errors.New("keystore/pkcs11: invalid user pin") 10 | ErrInvalidTokenLabel = errors.New("keystore/pkcs11: invalid token label") 11 | ErrInvalidSOPINLength = errors.New("keystore/pkcs11: invalid SO pin length, must be at least 4 characters") 12 | ErrInvalidPINLength = errors.New("keystore/pkcs11: invalid pin length, must be at least 4 characters") 13 | ) 14 | -------------------------------------------------------------------------------- /pkg/acme/datastore.go: -------------------------------------------------------------------------------- 1 | package acme 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao/afero" 9 | ) 10 | 11 | // Returns an ACME datastore using the backend specified in the datastore section of the 12 | // platform configuration file 13 | func NewDatastore(logger *logging.Logger, config *datastore.Config) (dao.Factory, error) { 14 | switch config.Backend { 15 | case datastore.BackendAferoFS.String(), datastore.BackendAferoMemory.String(): 16 | return afero.NewFactory(logger, config) 17 | default: 18 | return nil, datastore.ErrInvalidBackend 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/cmd/platform/policy_test.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Policy(t *testing.T) { 12 | 13 | // When the EK certificate handle is 0, the cert 14 | // is loaded from the x509 store instead of TPM 15 | // NV RAM 16 | app.DefaultConfig.TPMConfig.EK.CertHandle = 0 17 | 18 | InitParams.Initialize = true 19 | InitParams.Pin = []byte("test") 20 | InitParams.SOPin = []byte("test") 21 | InitParams.Env = app.EnvTest.String() 22 | 23 | App = app.DefaultTestConfig() 24 | 25 | response := executeCommand(PolicyCmd, []string{}) 26 | assert.True(t, strings.Contains(response, "Hash")) 27 | 28 | App.TPM.Close() 29 | } 30 | -------------------------------------------------------------------------------- /configs/firefox/policies.json: -------------------------------------------------------------------------------- 1 | { 2 | "policies": { 3 | "Certificates": { 4 | "ImportEnterpriseRoots": true, 5 | "Install": [ 6 | "/etc/firefox/certificates/root-ca.example.com.pkcs11.ecdsa.cer", 7 | "/etc/firefox/certificates/root-ca.example.com.pkcs11.rsa.cer", 8 | "/etc/firefox/certificates/root-ca.example.com.pkcs8.ecdsa.cer", 9 | "/etc/firefox/certificates/root-ca.example.com.pkcs8.ed25519.cer", 10 | "/etc/firefox/certificates/root-ca.example.com.pkcs8.rsa.cer", 11 | "/etc/firefox/certificates/root-ca.example.com.tpm2.ecdsa.cer", 12 | "/etc/firefox/certificates/root-ca.example.com.tpm2.rsa.cer" 13 | ] 14 | 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /pkg/store/keystore/password/clear.go: -------------------------------------------------------------------------------- 1 | package password 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 4 | 5 | type ClearPassword struct { 6 | password []byte 7 | keystore.Password 8 | } 9 | 10 | // Stores a clear text password 11 | func NewClearPassword(password []byte) keystore.Password { 12 | return ClearPassword{password: password} 13 | } 14 | 15 | func NewClearPasswordFromString(password string) keystore.Password { 16 | return ClearPassword{password: []byte(password)} 17 | } 18 | 19 | // Returns the password as a string 20 | func (p ClearPassword) String() (string, error) { 21 | return string(p.password), nil 22 | } 23 | 24 | // Returns the password as bytes 25 | func (p ClearPassword) Bytes() ([]byte, error) { 26 | return p.password, nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/device/entities/device.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 4 | 5 | type Device struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | 8 | AttestPub string `yaml:"att-pub" json:"att_pub"` 9 | EKCert string `yaml:"ek-cert" json:"ek_cert"` 10 | EventLog []byte `yaml:"event-log" json:"event_log"` 11 | HashAlgoId uint32 `yaml:"hash-algo" json:"hash_algo"` 12 | Model string `yaml:"model" json:"model"` 13 | Serial string `yaml:"serial" json:"serial"` 14 | SigningPub string `yaml:"signing-pub" json:"signing_pub"` 15 | 16 | entities.KeyValueEntity `yaml:"-" json:"-"` 17 | } 18 | 19 | func (d *Device) EntityID() uint64 { 20 | return d.ID 21 | } 22 | 23 | func (d *Device) SetEntityID(id uint64) { 24 | d.ID = id 25 | } 26 | -------------------------------------------------------------------------------- /pkg/serializer/serializer_json.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import "encoding/json" 4 | 5 | type JSONSerializer[E any] struct { 6 | Serializer[E] 7 | } 8 | 9 | func NewJSONSerializer[E any]() Serializer[E] { 10 | return &JSONSerializer[E]{} 11 | } 12 | 13 | func (js JSONSerializer[E]) Serialize(entity E) ([]byte, error) { 14 | return json.Marshal(entity) 15 | } 16 | 17 | func (js JSONSerializer[E]) Deserialize(data []byte, e any) error { 18 | if err := json.Unmarshal(data, e); err != nil { 19 | return err 20 | } 21 | return nil 22 | } 23 | 24 | func (js JSONSerializer[E]) Type() SerializerType { 25 | return SERIALIZER_JSON 26 | } 27 | 28 | func (js JSONSerializer[E]) Name() string { 29 | return "json" 30 | } 31 | 32 | func (js JSONSerializer[E]) Extension() string { 33 | return ".json" 34 | } 35 | -------------------------------------------------------------------------------- /pkg/store/keystore/shamir_test.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestShareAndCombine(t *testing.T) { 10 | 11 | secret := []byte("secret") 12 | shares := 5 13 | required := 3 14 | 15 | shards, err := ShareSecret(required, secret, shares) 16 | assert.Nil(t, err) 17 | assert.Equal(t, shares, len(shards)) 18 | 19 | valid := []string{ 20 | shards[0], 21 | shards[3], 22 | shards[4], 23 | } 24 | combinedSecret, err := SecretFromShares(valid) 25 | assert.Nil(t, err) 26 | assert.Equal(t, string(secret), combinedSecret) 27 | 28 | invalid := []string{ 29 | shards[0], 30 | shards[3], 31 | } 32 | nullSecret, err := SecretFromShares(invalid) 33 | assert.Nil(t, err) 34 | assert.NotEqual(t, string(secret), nullSecret) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/webservice/util.go: -------------------------------------------------------------------------------- 1 | package webservice 2 | 3 | import ( 4 | "os" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | func sanitizeHost(host string) string { 10 | sanitized := strings.ToLower(host) 11 | sanitized = strings.TrimSpace(sanitized) 12 | if strings.Contains(sanitized, ":") { 13 | sanitized = strings.Split(sanitized, ":")[0] 14 | } 15 | if !isValidHostname(sanitized) { 16 | return "" 17 | } 18 | return sanitized 19 | } 20 | 21 | func isValidHostname(hostname string) bool { 22 | validHostnamePattern := `^[a-z0-9-]+(\.[a-z0-9-]+)*$` 23 | matched, _ := regexp.MatchString(validHostnamePattern, hostname) 24 | return matched 25 | } 26 | 27 | func isFileAccessible(filePath string) bool { 28 | info, err := os.Stat(filePath) 29 | if err != nil { 30 | return false 31 | } 32 | return !info.IsDir() 33 | } 34 | -------------------------------------------------------------------------------- /pkg/webservice/v1/jwt/service_test.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGenerateToken(t *testing.T) { 13 | 14 | params := testServiceParams() 15 | params.KeyAttrs.KeyType = keystore.KEY_TYPE_TLS 16 | 17 | _, err := params.Keyring.GenerateKey(params.KeyAttrs) 18 | assert.Nil(t, err) 19 | 20 | service, err := NewService(params) 21 | assert.Nil(t, err) 22 | assert.NotNil(t, service) 23 | 24 | user := entities.NewUser("root@localhost") 25 | token, err := service.GenerateToken(user) 26 | assert.Nil(t, err) 27 | assert.NotNil(t, token) 28 | 29 | fmt.Println(token) 30 | } 31 | -------------------------------------------------------------------------------- /docs/man/attestor.md: -------------------------------------------------------------------------------- 1 | % tpadm attestor | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **attestor** - Starts the Attestor gRPC service 6 | 7 | # SYNOPSIS 8 | 9 | **attestor** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **attestor** - Starts the Attestor service to begin listening for inbound 14 | verification requests from the Verifier to begin Remote Attestation. 15 | 16 | 17 | # OPTIONS 18 | 19 | * **-l**, **\--listen**: 20 | 21 | The IP address, host name, or DNS name to listen for incoming gRPC requests. 22 | 23 | 24 | ## References 25 | 26 | [common options](common/options.md) collection of common options that provide 27 | information many users may expect. 28 | 29 | # NOTES 30 | 31 | 32 | # EXAMPLES 33 | 34 | ## Start the attestor service 35 | ```bash 36 | tpadm attestor 37 | ``` 38 | -------------------------------------------------------------------------------- /pkg/acme/entities/challenge.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type ACMEChallenge struct { 4 | ID uint64 `yaml:"id" json:"id"` 5 | Type string `yaml:"type" json:"type"` 6 | URL string `yaml:"url" json:"url"` 7 | Status string `yaml:"status" json:"status"` 8 | Token string `yaml:"token" json:"token"` 9 | Validated string `yaml:"validated" json:"validated,omitempty"` 10 | Error *Error `yaml:"error" json:"error,omitempty"` 11 | AccountID uint64 `yaml:"account-id" json:"account_id"` 12 | AuthorizationID uint64 `yaml:"authorization-id" json:"authorization_id"` 13 | } 14 | 15 | func (challenge *ACMEChallenge) SetEntityID(id uint64) { 16 | challenge.ID = id 17 | } 18 | 19 | func (challenge *ACMEChallenge) EntityID() uint64 { 20 | return challenge.ID 21 | } 22 | -------------------------------------------------------------------------------- /pkg/util/fs.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func IsEmpty(dir string) bool { 10 | f, err := os.Open(dir) 11 | if err != nil { 12 | return false 13 | } 14 | defer f.Close() 15 | if _, err = f.Readdirnames(1); err == io.EOF { 16 | return true 17 | } 18 | return false 19 | } 20 | 21 | func EscapeFilePath(path string) string { 22 | // List of characters to escape in Linux file paths 23 | specialChars := ` !"#$%&'()*,:;<=>?@[\]^` + "`" + `{|}~` 24 | 25 | // Replace each special character with an escaped version 26 | var escapedPath strings.Builder 27 | for _, ch := range path { 28 | if strings.ContainsRune(specialChars, ch) || ch == '\\' { 29 | escapedPath.WriteRune('\\') 30 | } 31 | escapedPath.WriteRune(ch) 32 | } 33 | return escapedPath.String() 34 | } 35 | -------------------------------------------------------------------------------- /pkg/serializer/serializer_yaml.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | YAML "gopkg.in/yaml.v2" 5 | ) 6 | 7 | type YAMLSerializer[E any] struct { 8 | Serializer[E] 9 | } 10 | 11 | func NewYAMLSerializer[E any]() Serializer[E] { 12 | return &YAMLSerializer[E]{} 13 | } 14 | 15 | func (js YAMLSerializer[E]) Serialize(entity E) ([]byte, error) { 16 | return YAML.Marshal(entity) 17 | } 18 | 19 | func (js YAMLSerializer[E]) Deserialize(data []byte, e any) error { 20 | if err := YAML.Unmarshal(data, e); err != nil { 21 | return err 22 | } 23 | return nil 24 | } 25 | 26 | func (js YAMLSerializer[E]) Type() SerializerType { 27 | return SERIALIZER_YAML 28 | } 29 | 30 | func (js YAMLSerializer[E]) Name() string { 31 | return "yaml" 32 | } 33 | 34 | func (js YAMLSerializer[E]) Extension() string { 35 | return ".yaml" 36 | } 37 | -------------------------------------------------------------------------------- /pkg/device/dao/afero/device.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | device_partition = "devices" 12 | ) 13 | 14 | type Device struct { 15 | *kvstore.AferoDAO[*entities.Device] 16 | } 17 | 18 | func NewDeviceDAO(params *datastore.Params[*entities.Device]) (dao.DeviceDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = device_profile_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Device{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /docs/man/ca/show.md: -------------------------------------------------------------------------------- 1 | % tpadm ca show | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca show** - Display an x509 certificate 6 | 7 | # SYNOPSIS 8 | 9 | **ca show** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca show** - Print x509 certificate details in human readable and PEM form. 14 | 15 | # OPTIONS 16 | 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | showrmation many users may expect. 22 | 23 | # NOTES 24 | 25 | # EXAMPLES 26 | 27 | ## Display certificate information 28 | ```bash 29 | tpadm ca show intermediate-ca.example.com 30 | ``` 31 | 32 | # AUTHOR 33 | Jeremy Hahn 34 | https://github.com/jeremyhahn 35 | https://www.linkdedin.com/in/jeremyhahn 36 | 37 | # COPYRIGHT 38 | (c) 2024 Jeremy Hahn 39 | All Rights Reserved 40 | -------------------------------------------------------------------------------- /pkg/ca/quantum_safe.go: -------------------------------------------------------------------------------- 1 | //go:build quantum_safe 2 | 3 | package ca 4 | 5 | import ( 6 | "crypto/x509/pkix" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/common" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 10 | ) 11 | 12 | // Returns the quantum safe extensions for the certificate. Only Dilitium2 is supported, 13 | // however, other algorithms will be added as they become officially supported by the 14 | // crypto community. 15 | func quantumSafeExtentions(algorithm keystore.QuantumAlgorithm, data []byte) []pkix.Extension { 16 | return []pkix.Extension{ 17 | { 18 | Id: common.OIDQuantumAlgorithm, 19 | Value: []byte(algorithm.String()), 20 | Critical: false, 21 | }, 22 | { 23 | Id: common.OIDQuantumSignature, 24 | Value: data, 25 | Critical: false, 26 | }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/man/tpm/info.md: -------------------------------------------------------------------------------- 1 | % tpadm tpm info | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **tpm info** - Display TPM information 6 | 7 | # SYNOPSIS 8 | 9 | **tpm info** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **tpm info** - Displays information about the TPM such as manufacturer, model number, firmware version, and handle usage. 14 | 15 | # OPTIONS 16 | 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | information many users may expect. 22 | 23 | # NOTES 24 | 25 | # EXAMPLES 26 | 27 | ## Retrieve TPM Information 28 | ```bash 29 | tpadm tpm info 30 | ``` 31 | 32 | # AUTHOR 33 | Jeremy Hahn 34 | https://github.com/jeremyhahn 35 | https://www.linkdedin.com/in/jeremyhahn 36 | 37 | # COPYRIGHT 38 | (c) 2024 Jeremy Hahn 39 | All Rights Reserved 40 | -------------------------------------------------------------------------------- /examples/tss/attestor/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "clean_trusted_data", 7 | "command": "rm", 8 | "args": [ 9 | "-rf", 10 | "pkg/trusted-data", 11 | ] 12 | }, 13 | { 14 | "type": "shell", 15 | "label": "copy_trusted_data", 16 | "command": "cp", 17 | "args": [ 18 | "-R", 19 | "trusted-data", 20 | "pkg/", 21 | ] 22 | }, 23 | { 24 | "type": "shell", 25 | "label": "attestor_clean_config", 26 | "dependsOn": [ 27 | "clean_trusted_data", 28 | "copy_trusted_data", 29 | ], 30 | } 31 | ]} -------------------------------------------------------------------------------- /docs/man/webservice.md: -------------------------------------------------------------------------------- 1 | % tpadm webservice | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **webservice** - Trusted Platform Web Services 6 | 7 | # SYNOPSIS 8 | 9 | **webservice** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **webservice** - Starts the embedded web server on the port specified in 14 | the platform configuration file. The web server hosts the static assets in the public_html directory as well as a REST API. 15 | 16 | The Swagger / OpenAPI REST API docs can be accessed by navigating to https://localhost:8443/swagger. 17 | 18 | 19 | # OPTIONS 20 | 21 | 22 | ## References 23 | 24 | [common options](common/options.md) collection of common options that provide 25 | information many users may expect. 26 | 27 | # NOTES 28 | 29 | 30 | # EXAMPLES 31 | 32 | ## Start the embedded web server 33 | ```bash 34 | tpadm webservice 35 | ``` 36 | -------------------------------------------------------------------------------- /pkg/acme/entities/authorization.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | AUTHORIZATION_INDEX_URL = "url" 5 | ) 6 | 7 | type ACMEAuthorization struct { 8 | ID uint64 `yaml:"id" json:"id"` 9 | Identifier ACMEIdentifier `yaml:"identifier" json:"identifier"` 10 | Status string `yaml:"status" json:"status"` 11 | Expires string `yaml:"expires" json:"expires"` 12 | Challenges []ACMEChallenge `yaml:"challenges" json:"challenges"` 13 | Wildcard bool `yaml:"wildcard" json:"wildcard"` 14 | AccountID uint64 `yaml:"account-id" json:"account_id"` 15 | OrderID uint64 16 | URL string `yaml:"url" json:"url"` 17 | } 18 | 19 | func (account *ACMEAuthorization) SetEntityID(id uint64) { 20 | account.ID = id 21 | } 22 | 23 | func (account *ACMEAuthorization) EntityID() uint64 { 24 | return account.ID 25 | } 26 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/nonce.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | acme_nonce_partition = "acme/nonces" 12 | ) 13 | 14 | type ACMENonceDAO struct { 15 | *kvstore.AferoDAO[*entities.ACMENonce] 16 | } 17 | 18 | func NewACMENonceDAO(params *datastore.Params[*entities.ACMENonce]) (dao.ACMENonceDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = acme_nonce_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO[*entities.ACMENonce](params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &ACMENonceDAO{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/config/attestation.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Attestation struct { 4 | AllowAttestorSelfCA bool `yaml:"allow-attestor-self-ca" json:"allow_attestor_self_ca" mapstructure:"allow-attestor-self-ca"` 5 | AllowOpenEnrollment bool `yaml:"allow-open-enrollment" json:"allow-open-enrollment" mapstructure:"allow-open-enrollment"` 6 | AllowedVerifiers []string `yaml:"allowed-verifiers" json:"allowed_verifiers" mapstructure:"allowed-verifiers"` 7 | InsecurePort int `yaml:"insecure-port" json:"insecure_port" mapstructure:"insecure-port"` 8 | InsecureSkipVerify bool `yaml:"insecure-skip-verify" json:"insecure_skip_verify" mapstructure:"insecure-skip-verify"` 9 | TLSPort int `yaml:"tls-port" json:"tls_port" mapstructure:"tls-port"` 10 | QuotePCRs []int32 `yaml:"quote-pcrs" json:"quote-pcrs" mapstructure:"quote-pcrs"` 11 | } 12 | -------------------------------------------------------------------------------- /docs/man/ca/info.md: -------------------------------------------------------------------------------- 1 | % tpadm ca info | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca info** - Displays Certificate Authority information 6 | 7 | # SYNOPSIS 8 | 9 | **ca info** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca info** - Displays key store, certificate store, and general information 14 | about a Certificate Authority. 15 | 16 | # OPTIONS 17 | 18 | 19 | ## References 20 | 21 | [common options](common/options.md) collection of common options that provide 22 | information many users may expect. 23 | 24 | # NOTES 25 | 26 | # EXAMPLES 27 | 28 | ## Display Certificate Authority Information 29 | ```bash 30 | tpadm ca info 31 | ``` 32 | 33 | # AUTHOR 34 | Jeremy Hahn 35 | https://github.com/jeremyhahn 36 | https://www.linkdedin.com/in/jeremyhahn 37 | 38 | # COPYRIGHT 39 | (c) 2024 Jeremy Hahn 40 | All Rights Reserved 41 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/registration.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/util" 4 | 5 | type Registration struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | Email string `yaml:"email" json:"email"` 8 | Password string `yaml:"password" json:"password"` 9 | OrgID uint64 `yaml:"org-id" json:"org_id"` 10 | OrgName string `yaml:"org-name" json:"org_name"` 11 | SessionData []byte `yaml:"session-data" json:"session_data"` 12 | KeyValueEntity `yaml:"-" json:"-"` 13 | } 14 | 15 | func NewRegistration(email string) *Registration { 16 | return &Registration{ 17 | ID: util.NewID([]byte(email)), 18 | Email: email, 19 | } 20 | } 21 | 22 | func (r *Registration) EntityID() uint64 { 23 | return r.ID 24 | } 25 | 26 | func (r *Registration) SetEntityID(id uint64) { 27 | r.ID = id 28 | } 29 | -------------------------------------------------------------------------------- /pkg/device/dao/afero/profile.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | device_profile_partition = "devices/profiles" 12 | ) 13 | 14 | type DeviceProfile struct { 15 | *kvstore.AferoDAO[*entities.DeviceProfile] 16 | } 17 | 18 | func NewDeviceProfileDAO(params *datastore.Params[*entities.DeviceProfile]) (dao.DeviceProfileDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = device_profile_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &DeviceProfile{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /docs/man/ca/init.md: -------------------------------------------------------------------------------- 1 | % tpadm ca init | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca init** - Initialize the Certificate Authorities 6 | 7 | # SYNOPSIS 8 | 9 | **ca init** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca init** - Initializes the Certificate Authority by creating a Root and 14 | Intermediates as specified in the platform configuration file. 15 | 16 | # OPTIONS 17 | 18 | 19 | ## References 20 | 21 | [common options](common/options.md) collection of common options that provide 22 | information many users may expect. 23 | 24 | # NOTES 25 | 26 | # EXAMPLES 27 | 28 | ## Initialize the Certificate Authority 29 | ```bash 30 | tpadm ca init 31 | ``` 32 | 33 | # AUTHOR 34 | Jeremy Hahn 35 | https://github.com/jeremyhahn 36 | https://www.linkdedin.com/in/jeremyhahn 37 | 38 | # COPYRIGHT 39 | (c) 2024 Jeremy Hahn 40 | All Rights Reserved 41 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/index_reference.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/jeremyhahn/go-trusted-platform/pkg/util" 4 | 5 | type Reference struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | ReferenceID uint64 `yaml:"ref-id" json:"ref_id"` 8 | name string `yaml:"-" json:"-"` 9 | Index `yaml:"-" json:"-"` 10 | } 11 | 12 | func NewReference(name string, referenceID uint64, referencedValue []byte) Index { 13 | return &Reference{ 14 | ID: util.NewID(referencedValue), 15 | ReferenceID: referenceID, 16 | name: name, 17 | } 18 | } 19 | 20 | func (ref *Reference) SetEntityID(id uint64) { 21 | ref.ID = id 22 | } 23 | 24 | func (ref *Reference) EntityID() uint64 { 25 | return ref.ID 26 | } 27 | 28 | func (ref *Reference) Name() string { 29 | return ref.name 30 | } 31 | 32 | func (ref *Reference) RefID() uint64 { 33 | return ref.ReferenceID 34 | } 35 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/certificate.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | acme_certificate_partition = "acme/certificates" 12 | ) 13 | 14 | type ACMECertificateDAO struct { 15 | *kvstore.AferoDAO[*entities.ACMECertificate] 16 | } 17 | 18 | func NewACMECertificateDAO(params *datastore.Params[*entities.ACMECertificate]) (dao.ACMECertificateDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = acme_certificate_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &ACMECertificateDAO{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/challenge.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | acme_challenge_partition = "acme/Challenges" 12 | ) 13 | 14 | type ACMEChallengeDAO struct { 15 | *kvstore.AferoDAO[*entities.ACMEChallenge] 16 | } 17 | 18 | func NewACMEChallengeDAO(params *datastore.Params[*entities.ACMEChallenge], accountID uint64) (dao.ACMEChallengeDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = acme_challenge_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &ACMEChallengeDAO{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/cmd/platform/provision_test.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Provision(t *testing.T) { 12 | 13 | // When the EK certificate handle is 0, the cert 14 | // is loaded from the x509 store instead of TPM 15 | // NV RAM 16 | app.DefaultConfig.TPMConfig.EK.CertHandle = 0 17 | 18 | InitParams.Pin = []byte("test") 19 | InitParams.SOPin = []byte("test") 20 | InitParams.Env = app.EnvTest.String() 21 | 22 | App = app.DefaultTestConfig() 23 | 24 | response := executeCommand(ProvisionCmd, []string{}) 25 | assert.True(t, strings.Contains(response, "Manufacturer: Microsoft")) 26 | assert.True(t, strings.Contains(response, "Vendor ID: xCG fTPM")) 27 | assert.True(t, strings.Contains(response, "FIPS 140-2: true")) 28 | 29 | App.TPM.Close() 30 | } 31 | -------------------------------------------------------------------------------- /examples/client/Makefile: -------------------------------------------------------------------------------- 1 | APPBIN ?= tpadm 2 | 3 | PLATFORM_DIR ?= trusted-data 4 | CONFIG_DIR ?= $(PLATFORM_DIR)/etc 5 | LOG_DIR ?= $(PLATFORM_DIR)/log 6 | CA_DIR ?= $(PLATFORM_DIR)/ca 7 | DEFAULT_PASSWORD ?= 123456 8 | 9 | default: clean build run 10 | 11 | env: 12 | @echo "APPBIN: \t\t$(APPBIN)" 13 | @echo "PLATFORM_DIR: \t\t$(PLATFORM_DIR)" 14 | @echo "CONFIG_DIR: \t\t$(CONFIG_DIR)" 15 | @echo "LOG_DIR: \t\t$(LOG_DIR)" 16 | @echo "CA_DIR: \t\t$(CA_DIR)" 17 | @echo "DEFAULT_PASSWORD: \t$(DEFAULT_PASSWORD)" 18 | 19 | build: 20 | cd ../../ && make 21 | 22 | 23 | clean: 24 | rm -rf $(PLATFORM_DIR) 25 | 26 | 27 | run: 28 | ../../$(APPBIN) webservice \ 29 | --debug \ 30 | --init \ 31 | --platform-dir $(PLATFORM_DIR) \ 32 | --log-dir $(LOG_DIR) \ 33 | --ca-dir $(CA_DIR) \ 34 | --raw-so-pin $(DEFAULT_PASSWORD) \ 35 | --raw-pin $(DEFAULT_PASSWORD) 36 | -------------------------------------------------------------------------------- /examples/server/Makefile: -------------------------------------------------------------------------------- 1 | APPBIN ?= tpadm 2 | 3 | PLATFORM_DIR ?= trusted-data 4 | CONFIG_DIR ?= $(PLATFORM_DIR)/etc 5 | LOG_DIR ?= $(PLATFORM_DIR)/log 6 | CA_DIR ?= $(PLATFORM_DIR)/ca 7 | DEFAULT_PASSWORD ?= 123456 8 | 9 | default: clean build run 10 | 11 | env: 12 | @echo "APPBIN: \t\t$(APPBIN)" 13 | @echo "PLATFORM_DIR: \t\t$(PLATFORM_DIR)" 14 | @echo "CONFIG_DIR: \t\t$(CONFIG_DIR)" 15 | @echo "LOG_DIR: \t\t$(LOG_DIR)" 16 | @echo "CA_DIR: \t\t$(CA_DIR)" 17 | @echo "DEFAULT_PASSWORD: \t$(DEFAULT_PASSWORD)" 18 | 19 | build: 20 | cd ../../ && make 21 | 22 | 23 | clean: 24 | rm -rf $(PLATFORM_DIR) 25 | 26 | 27 | run: 28 | ../../$(APPBIN) webservice \ 29 | --debug \ 30 | --init \ 31 | --platform-dir $(PLATFORM_DIR) \ 32 | --log-dir $(LOG_DIR) \ 33 | --ca-dir $(CA_DIR) \ 34 | --raw-so-pin $(DEFAULT_PASSWORD) \ 35 | --raw-pin $(DEFAULT_PASSWORD) 36 | -------------------------------------------------------------------------------- /pkg/dns/datastore.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/dao/afero" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/entities" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | ) 9 | 10 | type Datastore struct { 11 | params *datastore.Params[*entities.Zone] 12 | storeType datastore.StoreType 13 | } 14 | 15 | func NewDatastore( 16 | params *datastore.Params[*entities.Zone], 17 | storeType datastore.StoreType) *Datastore { 18 | 19 | return &Datastore{ 20 | params: params, 21 | storeType: storeType, 22 | } 23 | } 24 | 25 | func (ds *Datastore) ZoneDAO() (dao.ZoneDAO, error) { 26 | switch ds.storeType { 27 | case datastore.BackendAferoFS, datastore.BackendAferoMemory: 28 | return afero.NewZoneDAO(ds.params) 29 | default: 30 | panic("not implemented") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(versionCmd) 12 | } 13 | 14 | var versionCmd = &cobra.Command{ 15 | Use: "version", 16 | Short: "Prints the software version", 17 | Long: `Displays software build and version details`, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | fmt.Printf("Name:\t\t\t%s\n", app.Name) 20 | fmt.Printf("Version:\t\t%s\n", app.Version) 21 | fmt.Printf("Repository:\t\t%s\n", app.Repository) 22 | fmt.Printf("Package:\t\t%s\n", app.Package) 23 | fmt.Printf("Git Branch:\t\t%s\n", app.GitBranch) 24 | fmt.Printf("Git Tag:\t\t%s\n", app.GitTag) 25 | fmt.Printf("Git Hash:\t\t%s\n", app.GitHash) 26 | fmt.Printf("Build User:\t\t%s\n", app.BuildUser) 27 | fmt.Printf("Build Date:\t\t%s\n", app.BuildDate) 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /pkg/cmd/platform.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/cmd/platform" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | subcmd "github.com/jeremyhahn/go-trusted-platform/pkg/cmd/platform" 9 | ) 10 | 11 | func init() { 12 | 13 | platformCmd.AddCommand(platform.DestroyCmd) 14 | platformCmd.AddCommand(platform.KeyringCmd) 15 | platformCmd.AddCommand(platform.PolicyCmd) 16 | platformCmd.AddCommand(platform.ProvisionCmd) 17 | platformCmd.AddCommand(platform.InstallCmd) 18 | 19 | rootCmd.AddCommand(platformCmd) 20 | } 21 | 22 | func initPlatform() { 23 | 24 | subcmd.App = App 25 | subcmd.InitParams = InitParams 26 | subcmd.CAParams = CAParams 27 | } 28 | 29 | var platformCmd = &cobra.Command{ 30 | Use: "platform", 31 | Short: "Platform Operations", 32 | Long: `Perform Platform Administrator operations`, 33 | // Run: func(cmd *cobra.Command, args []string) { 34 | // }, 35 | } 36 | -------------------------------------------------------------------------------- /pkg/app/version.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | var ( 4 | Name, 5 | Repository, 6 | Package, 7 | Version, 8 | BuildDate, 9 | BuildUser, 10 | GitBranch, 11 | GitTag, 12 | GitHash, 13 | Image string 14 | ) 15 | 16 | type AppVersion struct { 17 | Name string `json:"name"` 18 | Repository string `json:"repository"` 19 | Package string `json:"package"` 20 | Version string `json:"version"` 21 | GitBranch string `json:"gitBranch"` 22 | GitTag string `json:"gitTag"` 23 | GitHash string `json:"gitHash"` 24 | BuildDate string `json:"buildDate"` 25 | BuildUser string `json:"buildUser"` 26 | } 27 | 28 | func GetVersion() *AppVersion { 29 | return &AppVersion{ 30 | Name: Name, 31 | Repository: Repository, 32 | Package: Package, 33 | Version: Version, 34 | GitBranch: GitBranch, 35 | GitTag: GitTag, 36 | GitHash: GitHash, 37 | BuildDate: BuildDate, 38 | BuildUser: BuildUser} 39 | } 40 | -------------------------------------------------------------------------------- /pkg/device/datastore.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/dao/afero" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/entities" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | ) 9 | 10 | type Datastore struct { 11 | params *datastore.Params[*entities.Device] 12 | storeType datastore.StoreType 13 | } 14 | 15 | func NewDatastore( 16 | params *datastore.Params[*entities.Device], 17 | storeType datastore.StoreType) *Datastore { 18 | 19 | return &Datastore{ 20 | params: params, 21 | storeType: storeType, 22 | } 23 | } 24 | 25 | func (ds *Datastore) DeviceDAO() (dao.DeviceDAO, error) { 26 | switch ds.storeType { 27 | case datastore.BackendAferoFS, datastore.BackendAferoMemory: 28 | return afero.NewDeviceDAO(ds.params) 29 | default: 30 | panic("not implemented") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/util/fs_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEscapeFilePath(t *testing.T) { 8 | tests := []struct { 9 | input string 10 | expected string 11 | }{ 12 | { 13 | input: "(STAGING) False Fennel E6", 14 | expected: `\(STAGING\)\ False\ Fennel\ E6`, 15 | }, 16 | { 17 | input: `Special chars !@#$%^&*()`, 18 | expected: `Special\ chars\ \!\@\#\$\%\^\&\*\(\)`, 19 | }, 20 | { 21 | input: `NoSpecialChars`, 22 | expected: `NoSpecialChars`, 23 | }, 24 | { 25 | input: `path/with spaces`, 26 | expected: `path/with\ spaces`, 27 | }, 28 | { 29 | input: `back\slash\test`, 30 | expected: `back\\slash\\test`, 31 | }, 32 | } 33 | 34 | for _, test := range tests { 35 | result := EscapeFilePath(test.input) 36 | if result != test.expected { 37 | t.Errorf("EscapeFilePath(%q) = %q; want %q", test.input, result, test.expected) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/cmd/ca/install.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/prompt" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var InstallCmd = &cobra.Command{ 10 | Use: "install-ca-certificates", 11 | Short: "Install Certificate Authority Certificates", 12 | Long: `Installs the Root and Intermediate Certificate Authority certificates 13 | to the operating system trusted certificate store.`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | prompt.PrintBanner(app.Version) 17 | 18 | App, err = App.Init(InitParams) 19 | if err != nil { 20 | cmd.PrintErrln(err) 21 | return 22 | } 23 | 24 | intermediateCN := App.CA.Identity().Subject.CommonName 25 | if err := App.CA.OSTrustStore().Install(intermediateCN); err != nil { 26 | cmd.PrintErrln(err) 27 | } 28 | 29 | cmd.Println("CA certificates successfully installed") 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/eventlog.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | tpmEventLogPath string 11 | ) 12 | 13 | func init() { 14 | EventLogCmd.PersistentFlags().StringVar(&tpmEventLogPath, "path", "/sys/kernel/security/tpm0/binary_bios_measurements", "The path to the system measurement log") 15 | } 16 | 17 | var EventLogCmd = &cobra.Command{ 18 | Use: "eventlog", 19 | Short: "Dumps the local binary_bios_measurements log", 20 | Long: `Dumps the TPM system measurement log. Read permissions are required to the 21 | measurement file (/sys/kernel/security/tpm0/binary_bios_measurements).`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | events, err := tpm2.ParseEventLog(tpmEventLogPath) 24 | if err != nil { 25 | color.New(color.FgRed).Println(err) 26 | return 27 | } 28 | tpm2.PrintEvents(events) 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /pkg/store/datastore/serializer_test.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/serializer" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSerializers(t *testing.T) { 13 | 14 | org := entities.NewOrganization("test") 15 | 16 | serializers := []serializer.Serializer[*entities.Organization]{ 17 | serializer.NewJSONSerializer[*entities.Organization](), 18 | serializer.NewYAMLSerializer[*entities.Organization](), 19 | } 20 | 21 | for _, serializer := range serializers { 22 | bytes, err := serializer.Serialize(org) 23 | assert.Nil(t, err) 24 | assert.NotNil(t, bytes) 25 | 26 | fmt.Println(string(bytes)) 27 | 28 | org := &entities.Organization{} 29 | err = serializer.Deserialize(bytes, org) 30 | assert.Nil(t, err) 31 | assert.Equal(t, "test", org.Name) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/tss/verifier/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "verifier (clean, config, init)", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "preLaunchTask": "verifier_clean_config", 10 | "program": "${workspaceRoot}/pkg", 11 | "args": [ 12 | "-init=true", 13 | "-attestor", "www.attestor.local", 14 | "-so-pin", "123456", 15 | "-pin", "123456", 16 | ], 17 | }, { 18 | "name": "verifier", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "debug", 22 | "program": "${workspaceRoot}/pkg", 23 | "args": [ 24 | "-listen", "localhost:8090", 25 | "-so-pin", "123456", 26 | "-pin", "123456", 27 | ], 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/tss/attestor/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "attestor (clean_config)", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "preLaunchTask": "attestor_clean_config", 10 | "program": "${workspaceRoot}/pkg", 11 | "args": [ 12 | "-init=true", 13 | "-listen", "localhost:8090", 14 | "-so-pin", "123456", 15 | "-pin", "123456", 16 | ], 17 | }, { 18 | "name": "attestor", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "debug", 22 | "program": "${workspaceRoot}/pkg", 23 | "args": [ 24 | "-init=true", 25 | "-listen", "localhost:8090", 26 | "-so-pin", "123456", 27 | "-pin", "123456", 28 | ], 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /pkg/store/certstore/backend_blobstore.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "crypto/x509" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/blob" 7 | ) 8 | 9 | type BlobStoreBackend struct { 10 | blobStore blob.BlobStorer 11 | CertificateBackend 12 | } 13 | 14 | func NewBlobStoreBackend(blobStore blob.BlobStorer) CertificateBackend { 15 | return &BlobStoreBackend{ 16 | blobStore: blobStore, 17 | } 18 | } 19 | 20 | func (bse *BlobStoreBackend) ImportCertificate( 21 | id []byte, certificate *x509.Certificate) error { 22 | 23 | return bse.blobStore.Save(id, certificate.Raw) 24 | } 25 | 26 | func (bse *BlobStoreBackend) Get(id []byte) (*x509.Certificate, error) { 27 | der, err := bse.blobStore.Get(id) 28 | if err != nil { 29 | if err == blob.ErrBlobNotFound { 30 | return nil, ErrCertNotFound 31 | } 32 | return nil, err 33 | } 34 | return x509.ParseCertificate(der) 35 | } 36 | 37 | func (bse *BlobStoreBackend) DeleteCertificate(id []byte) error { 38 | return bse.blobStore.Delete(id) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/store/keystore/signer_opts.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | 7 | blobstore "github.com/jeremyhahn/go-trusted-platform/pkg/store/blob" 8 | ) 9 | 10 | type SignerOpts struct { 11 | Backend KeyBackend 12 | KeyAttributes *KeyAttributes 13 | 14 | // Optional PSS Salt Length when using RSA PSS. 15 | // Default rsa.PSSSaltLengthEqualsHash 16 | PSSOptions *rsa.PSSOptions 17 | BlobCN []byte 18 | BlobData []byte 19 | 20 | blobStore blobstore.BlobStorer 21 | crypto.SignerOpts 22 | } 23 | 24 | func NewSignerOpts( 25 | attrs *KeyAttributes, 26 | data []byte) *SignerOpts { 27 | 28 | return &SignerOpts{ 29 | KeyAttributes: attrs, 30 | BlobData: data, 31 | } 32 | } 33 | 34 | func (opts SignerOpts) HashFunc() crypto.Hash { 35 | return opts.KeyAttributes.Hash 36 | } 37 | 38 | func (opts SignerOpts) Digest() ([]byte, error) { 39 | digest, err := Digest(opts.KeyAttributes.Hash, opts.BlobData) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return digest, nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/cmd/tpm/provision.go: -------------------------------------------------------------------------------- 1 | package tpm 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var ( 8 | hierarchyAuth string 9 | ) 10 | 11 | var ProvisionCmd = &cobra.Command{ 12 | Use: "provision", 13 | Short: "Provision Trusted Platform Module", 14 | Long: `Provisions a Trusted Platform Module in alignment with the TCG 15 | provisioning guidance.`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | 18 | InitParams.Initialize = true 19 | App, err = App.Init(InitParams) 20 | if err != nil { 21 | cmd.PrintErrln(err) 22 | return 23 | } 24 | 25 | ekAttrs, err := App.TPM.EKAttributes() 26 | if err != nil { 27 | cmd.PrintErrln(err) 28 | return 29 | } 30 | cmd.Println(ekAttrs) 31 | 32 | ssrkAttrs, err := App.TPM.SSRKAttributes() 33 | if err != nil { 34 | cmd.PrintErrln(err) 35 | return 36 | } 37 | cmd.Println(ssrkAttrs) 38 | 39 | iakAttrs, err := App.TPM.IAKAttributes() 40 | if err != nil { 41 | cmd.PrintErrln(err) 42 | return 43 | } 44 | cmd.Println(iakAttrs) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /docs/man/platform/destroy.md: -------------------------------------------------------------------------------- 1 | % tpadm platform destroy | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **platform destroy** - Destroy the platform data and configurations 6 | 7 | # SYNOPSIS 8 | 9 | **platform destroy** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **platform destroy** - This command deletes all platform data, including Certificate Authority keys, certifiates, secrets, and blob storage. A TPM2_Clear command is sent to the TPM, restoring it to the manufacturer's factory settings. 14 | 15 | 16 | # OPTIONS 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | information many users may expect. 22 | 23 | # NOTES 24 | 25 | 26 | # EXAMPLES 27 | 28 | ## Clear the TPM and destroy all platform data 29 | ```bash 30 | tpadm platform destroy 31 | ``` 32 | 33 | # AUTHOR 34 | Jeremy Hahn 35 | https://github.com/jeremyhahn 36 | https://www.linkdedin.com/in/jeremyhahn 37 | 38 | # COPYRIGHT 39 | (c) 2024 Jeremy Hahn 40 | All Rights Reserved 41 | -------------------------------------------------------------------------------- /examples/tss/verifier/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "make_config", 7 | "command": "make", 8 | "args": ["config"] 9 | }, 10 | { 11 | "type": "shell", 12 | "label": "clean_trusted_data", 13 | "command": "rm", 14 | "args": [ 15 | "-rf", 16 | "pkg/trusted-data", 17 | ] 18 | }, 19 | { 20 | "type": "shell", 21 | "label": "copy_trusted_data", 22 | "command": "cp", 23 | "args": [ 24 | "-R", 25 | "trusted-data", 26 | "pkg/", 27 | ] 28 | }, 29 | { 30 | "type": "shell", 31 | "label": "verifier_clean_config", 32 | "dependsOn": [ 33 | "clean_trusted_data", 34 | "make_config", 35 | "copy_trusted_data", 36 | ], 37 | } 38 | ]} -------------------------------------------------------------------------------- /docs/man/tpm/ek.md: -------------------------------------------------------------------------------- 1 | % tpadm tpm ek | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **tpm ek** - Retrieve the Endorsement Public Key 6 | 7 | # SYNOPSIS 8 | 9 | **tpm ek** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **tpm ek** - Retrieve's the TPM Endorsement Public Key in PEM form 14 | 15 | # OPTIONS 16 | 17 | * **-r**, **\--rsa**: 18 | 19 | Retrieve the RSA Endorsement Key 20 | 21 | * **-e**, **\--ecc**: 22 | 23 | Retrieve the ECC Endorsement Key 24 | 25 | ## References 26 | 27 | [common options](common/options.md) collection of common options that provide 28 | information many users may expect. 29 | 30 | # NOTES 31 | 32 | # EXAMPLES 33 | 34 | ## Retrieve TPM 2.0 RSA Endorsement Public Key 35 | ```bash 36 | tpadm tpm ek -r 37 | ``` 38 | 39 | ## Retrieve TPM 2.0 ECC Endorsement Public Key 40 | ```bash 41 | tpadm tpm ek -e 42 | ``` 43 | 44 | # AUTHOR 45 | Jeremy Hahn 46 | https://github.com/jeremyhahn 47 | https://www.linkdedin.com/in/jeremyhahn 48 | 49 | # COPYRIGHT 50 | (c) 2024 Jeremy Hahn 51 | All Rights Reserved 52 | -------------------------------------------------------------------------------- /pkg/acme/entities/account.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type ACMEAccount struct { 6 | ID uint64 `yaml:"id" json:"id"` 7 | Contact []string `yaml:"contact" json:"contact,omitempty"` 8 | CreatedAt time.Time `yaml:"created" json:"created"` 9 | ExternalAccountBinding interface{} `yaml:"externalAccountBinding" json:"externalAccountBinding,omitempty"` 10 | Key string `yaml:"key" json:"key"` 11 | OnlyReturnExisting bool `yaml:"onlyReturnExisting" json:"onlyReturnExisting,omitempty"` 12 | Orders string `yaml:"orders" json:"orders"` 13 | Status string `yaml:"status" json:"status"` 14 | TermsOfServiceAgreed bool `yaml:"termsOfServiceAgreed" json:"termsOfServiceAgreed,omitempty"` 15 | URL string `yaml:"url" json:"url"` 16 | } 17 | 18 | func (account *ACMEAccount) SetEntityID(id uint64) { 19 | account.ID = id 20 | } 21 | 22 | func (account *ACMEAccount) EntityID() uint64 { 23 | return account.ID 24 | } 25 | -------------------------------------------------------------------------------- /pkg/crypto/argon2/argon2id_test.go: -------------------------------------------------------------------------------- 1 | package argon2 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestArgon2(t *testing.T) { 12 | 13 | password := "$ecret!" 14 | passwordHasher := NewArgon2(rand.Reader) 15 | hash, err := passwordHasher.Hash(password) 16 | assert.Nil(t, err) 17 | assert.NotNil(t, hash) 18 | 19 | match, err := passwordHasher.Compare(password, hash) 20 | assert.Nil(t, err) 21 | assert.True(t, match) 22 | 23 | fmt.Println(hash) 24 | } 25 | 26 | func TestCreateArgon2(t *testing.T) { 27 | 28 | password := "$ecret!" 29 | 30 | params := Argon2Config{ 31 | Memory: 1, 32 | Iterations: 1, 33 | Parallelism: 1, 34 | SaltLength: 16, 35 | KeyLength: 32} 36 | 37 | passwordHasher := CreateArgon2(rand.Reader, params) 38 | hash, err := passwordHasher.Hash(password) 39 | assert.Nil(t, err) 40 | assert.NotNil(t, hash) 41 | 42 | match, err := passwordHasher.Compare(password, hash) 43 | assert.Nil(t, err) 44 | assert.True(t, match) 45 | 46 | fmt.Println(hash) 47 | } 48 | -------------------------------------------------------------------------------- /docs/man/ca/issue.md: -------------------------------------------------------------------------------- 1 | % tpadm ca issue | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca issue** - Displays Certificate Authority issuermation 6 | 7 | # SYNOPSIS 8 | 9 | **ca issue** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca issue** - Issue a new TLS certificate 14 | 15 | # OPTIONS 16 | 17 | ## References 18 | 19 | [common options](common/options.md) collection of common options that provide 20 | issuermation many users may expect. 21 | 22 | # NOTES 23 | 24 | # EXAMPLES 25 | 26 | ## Issue new TPM 2.0 RSA TLS certificate 27 | ```bash 28 | tpadm ca issue webserver.mydomain.com tpm2 rsa 29 | ``` 30 | 31 | ## Issue new PKCS #11 ECDSA TLS certificate 32 | ```bash 33 | tpadm ca issue webserver.mydomain.com pkcs11 ecdsa 34 | ``` 35 | 36 | ## Issue new PKCS #8 Ed25519 TLS certificate 37 | ```bash 38 | tpadm ca issue webserver.mydomain.com pkcs8 ed25519 39 | ``` 40 | 41 | # AUTHOR 42 | Jeremy Hahn 43 | https://github.com/jeremyhahn 44 | https://www.linkdedin.com/in/jeremyhahn 45 | 46 | # COPYRIGHT 47 | (c) 2024 Jeremy Hahn 48 | All Rights Reserved 49 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/new_nonce.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 8 | ) 9 | 10 | func (s *RestService) NewNonceHandler(w http.ResponseWriter, r *http.Request) { 11 | 12 | s.logger.Debug("NewNonceHandler", "method", r.Method, "url", r.URL) 13 | for name, values := range r.Header { 14 | for _, value := range values { 15 | s.logger.Debugf("%s: %s\n", name, value) 16 | } 17 | } 18 | 19 | nonce, err := acme.GenerateNonce(nonceSize) 20 | if err != nil { 21 | writeError(w, acme.ServerInternal("Unable to generate nonce")) 22 | return 23 | } 24 | s.nonceStore.Add(nonce) 25 | 26 | w.Header().Set("Replay-Nonce", nonce) 27 | w.Header().Set("Cache-Control", "no-store") 28 | w.Header().Set("Link", fmt.Sprintf("%s/acme/directory>;rel=\"index\"", s.baseRESTURI)) 29 | 30 | if r.Method == http.MethodHead { 31 | w.WriteHeader(http.StatusOK) 32 | } else if r.Method == http.MethodGet { 33 | w.WriteHeader(http.StatusNoContent) 34 | } else { 35 | w.WriteHeader(http.StatusMethodNotAllowed) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/util/leakybucket.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type LeakyBucket struct { 9 | capacity int 10 | leakInterval time.Duration 11 | tokens int 12 | lastLeakTime time.Time 13 | mu sync.Mutex 14 | } 15 | 16 | // Creates a new rate limiter using the leaky bucket algorithm 17 | func NewLeakyBucket(capacity int, window time.Duration) *LeakyBucket { 18 | leakInterval := window / time.Duration(capacity) 19 | return &LeakyBucket{ 20 | capacity: capacity, 21 | leakInterval: leakInterval, 22 | tokens: capacity, 23 | lastLeakTime: time.Now(), 24 | } 25 | } 26 | 27 | func (b *LeakyBucket) AllowRequest() bool { 28 | b.mu.Lock() 29 | defer b.mu.Unlock() 30 | 31 | now := time.Now() 32 | elapsed := now.Sub(b.lastLeakTime) 33 | leakedTokens := int(elapsed / b.leakInterval) 34 | if leakedTokens > 0 { 35 | b.tokens -= leakedTokens 36 | if b.tokens < 0 { 37 | b.tokens = 0 38 | } 39 | b.lastLeakTime = now 40 | } 41 | 42 | if b.tokens < b.capacity { 43 | b.tokens++ 44 | return true 45 | } 46 | 47 | return false 48 | } 49 | -------------------------------------------------------------------------------- /docs/man/ca/uninstall-ca-certificates.md: -------------------------------------------------------------------------------- 1 | % tpadm ca uninstall-ca-certificates | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca uninstall-ca-certificates** - Uninstalls the Certificate Authority Certificates 6 | 7 | # SYNOPSIS 8 | 9 | **ca uninstall-ca-certificates** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca uninstall-ca-certificates** - Deletes the Root and Intermediate Certificate Authority certificates from the operating system trusted certificate store. 14 | 15 | # OPTIONS 16 | 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | information many users may expect. 22 | 23 | # NOTES 24 | 25 | This command requires root or sudo privileges. 26 | 27 | # EXAMPLES 28 | 29 | ## Uninstall Certificate Authority certificates from local OS trust store 30 | ```bash 31 | sudo tpadm ca uninstall-ca-certificates 32 | ``` 33 | 34 | # AUTHOR 35 | Jeremy Hahn 36 | https://github.com/jeremyhahn 37 | https://www.linkdedin.com/in/jeremyhahn 38 | 39 | # COPYRIGHT 40 | (c) 2024 Jeremy Hahn 41 | All Rights Reserved 42 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/directory.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 9 | ) 10 | 11 | // DirectoryHandler responds to /acme/directory requests. 12 | func (s *RestService) DirectoryHandler(w http.ResponseWriter, r *http.Request) { 13 | tos := fmt.Sprintf("%s%s", s.baseRESTURI, s.params.ACMEConfig.Server.TermsOfService) 14 | directory := acme.Directory{ 15 | NewNonce: fmt.Sprintf("%s/acme/new-nonce", s.baseRESTURI), 16 | NewAccount: fmt.Sprintf("%s/acme/new-account", s.baseRESTURI), 17 | NewOrder: fmt.Sprintf("%s/acme/new-order", s.baseRESTURI), 18 | RevokeCert: fmt.Sprintf("%s/acme/revoke-cert", s.baseRESTURI), 19 | KeyChange: fmt.Sprintf("%s/acme/key-change", s.baseRESTURI), 20 | Meta: acme.Meta{ 21 | TermsOfService: tos, 22 | Website: s.baseRESTURI, 23 | CAAIdentities: []string{s.params.CN}, 24 | ExternalAccountReq: false, 25 | }, 26 | } 27 | w.Header().Set("Content-Type", "application/json") 28 | w.WriteHeader(http.StatusOK) 29 | json.NewEncoder(w).Encode(directory) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cmd/ca/uninstall.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var UninstallCmd = &cobra.Command{ 8 | Use: "uninstall-ca-certificates", 9 | Short: "Uninstalls the Certificate Authority Certificates", 10 | Long: `Deletes the Root and Intermediate Certificate Authority certificates 11 | from the operating system trusted certificate store.`, 12 | Run: func(cmd *cobra.Command, args []string) { 13 | 14 | App, err = App.Init(InitParams) 15 | if err != nil { 16 | cmd.PrintErrln(err) 17 | return 18 | } 19 | 20 | if App.CA == nil { 21 | soPIN, userPIN, err := App.ParsePINs(InitParams.SOPin, InitParams.Pin) 22 | if err != nil { 23 | App.Logger.Error(err) 24 | cmd.PrintErrln(err) 25 | return 26 | } 27 | if err := App.LoadCA(soPIN, userPIN); err != nil { 28 | cmd.PrintErrln(err) 29 | return 30 | } 31 | } 32 | 33 | intermediateCN := App.CA.Identity().Subject.CommonName 34 | if err := App.CA.OSTrustStore().Uninstall(intermediateCN); err != nil { 35 | cmd.PrintErrln(err) 36 | } 37 | 38 | cmd.Println("CA certificates successfully uninstalled") 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /pkg/acme/entities/errors.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | // Error represents an ACME error as per RFC 8555. 4 | type Error struct { 5 | Type string `json:"type"` 6 | Detail string `json:"detail"` 7 | Status int `json:"-"` 8 | SubProblems []SubProblem `json:"subproblems,omitempty"` 9 | Identifier *ACMEIdentifier `json:"identifier,omitempty"` 10 | Instance string `json:"instance,omitempty"` 11 | } 12 | 13 | // Implements the error interface 14 | func (e *Error) Error() string { 15 | return e.Detail 16 | } 17 | 18 | // NewError creates a new ACME error. 19 | func NewError(errType, detail string, status int, subproblems []SubProblem) *Error { 20 | return &Error{ 21 | Type: "urn:ietf:params:acme:error:" + errType, 22 | Detail: detail, 23 | Status: status, 24 | SubProblems: subproblems, 25 | } 26 | } 27 | 28 | // SubProblem represents a subproblem in a compound error per RFC 8555. 29 | type SubProblem struct { 30 | Type string `json:"type"` 31 | Detail string `json:"detail"` 32 | Identifier *ACMEIdentifier `json:"identifier,omitempty"` 33 | } 34 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/blob.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | blob_partition = "blob" 5 | ) 6 | 7 | type Blob struct { 8 | ID uint64 `yaml:"id" json:"id"` 9 | Bytes []byte `yaml:"bytes" json:"bytes"` 10 | Entity interface{} `yaml:"entity" json:"entity"` 11 | partition string `yaml:"-" json:"-"` 12 | KeyValueEntity `yaml:"-" json:"-"` 13 | } 14 | 15 | func NewBlob(id uint64, bytes []byte) *Blob { 16 | return &Blob{ 17 | ID: id, 18 | Bytes: bytes, 19 | partition: blob_partition, 20 | } 21 | } 22 | 23 | func CreateBlob(id uint64, bytes []byte, partition string) *Blob { 24 | return &Blob{ 25 | ID: id, 26 | Bytes: bytes, 27 | partition: partition, 28 | } 29 | } 30 | 31 | func (blob *Blob) SetEntityID(id uint64) { 32 | blob.ID = id 33 | } 34 | 35 | func (blob *Blob) EntityID() uint64 { 36 | return blob.ID 37 | } 38 | 39 | func (blob *Blob) SetEntity(entity interface{}) { 40 | blob.Entity = entity 41 | } 42 | 43 | func (blob *Blob) SetPartition(partition string) { 44 | blob.partition = partition 45 | } 46 | 47 | func (blob *Blob) Partition() string { 48 | return blob.partition 49 | } 50 | -------------------------------------------------------------------------------- /pkg/dns/dao/afero/zone.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/dns/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | dns_zones_partition = "dns/zones" 12 | ) 13 | 14 | type Zone struct { 15 | *kvstore.AferoDAO[*entities.Zone] 16 | } 17 | 18 | func NewZoneDAO(params *datastore.Params[*entities.Zone]) (dao.ZoneDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = dns_zones_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Zone{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | 31 | func (z *Zone) GetByName(name string, consistencyLevel datastore.ConsistencyLevel) (*entities.Zone, error) { 32 | zones, err := z.Page(datastore.NewPageQuery(), consistencyLevel) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | for _, zone := range zones.Entities { 38 | if zone.Name == name { 39 | return zone, nil 40 | } 41 | } 42 | return nil, err 43 | } 44 | -------------------------------------------------------------------------------- /pkg/tpm2/random_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestRandBytes(t *testing.T) { 12 | 13 | _, tpm := createSim(false, true) 14 | defer tpm.Close() 15 | 16 | randomBytes := make([]byte, 32) 17 | 18 | n, err := tpm.Read(randomBytes) 19 | assert.Nil(t, err) 20 | assert.Equal(t, 32, n) 21 | assert.Equal(t, len(randomBytes), 32) 22 | } 23 | 24 | func TestRandBytesEncrypted(t *testing.T) { 25 | 26 | _, tpm := createSim(true, true) 27 | defer tpm.Close() 28 | 29 | randomBytes := make([]byte, 32) 30 | 31 | n, err := tpm.Read(randomBytes) 32 | assert.Nil(t, err) 33 | assert.Equal(t, 32, n) 34 | assert.Equal(t, len(randomBytes), 32) 35 | } 36 | 37 | func TestRandom(t *testing.T) { 38 | 39 | logger := logging.DefaultLogger() 40 | 41 | _, tpm := createSim(false, false) 42 | defer tpm.Close() 43 | 44 | random, err := tpm.Random() 45 | assert.Nil(t, err) 46 | assert.NotNil(t, random) 47 | assert.Equal(t, 32, len(random)) 48 | 49 | encoded := hex.EncodeToString(random) 50 | 51 | logger.Debugf("%+s", encoded) 52 | } 53 | -------------------------------------------------------------------------------- /docs/man/ca/revoke.md: -------------------------------------------------------------------------------- 1 | % tpadm ca revoke | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca revoke** - Revokes an issued certificate 6 | 7 | # SYNOPSIS 8 | 9 | **ca revoke** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca revoke** - Add the certificate to the CA Certificate Revocation List and delete the certificate and keys from the backend stores. 14 | 15 | # OPTIONS 16 | 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | revokermation many users may expect. 22 | 23 | # NOTES 24 | 25 | # EXAMPLES 26 | 27 | ## Revoke RSA TPM 2.0 end entity certificate 28 | ```bash 29 | tpadm ca revoke webserver.mydomain.com tpm2 rsa 30 | ``` 31 | 32 | ## Revoke ECDSA PKCS #11 end entity certificate 33 | ```bash 34 | tpadm ca revoke webserver.mydomain.com pkcs11 ecdsa 35 | ``` 36 | 37 | ## Remove PKCS #8 Ed25519 end entity certificate 38 | ```bash 39 | tpadm ca revoke webserver.mydomain.com pkcs8 ed25519 40 | ``` 41 | 42 | # AUTHOR 43 | Jeremy Hahn 44 | https://github.com/jeremyhahn 45 | https://www.linkdedin.com/in/jeremyhahn 46 | 47 | # COPYRIGHT 48 | (c) 2024 Jeremy Hahn 49 | All Rights Reserved 50 | -------------------------------------------------------------------------------- /docs/man/ca/install-ca-certificates.md: -------------------------------------------------------------------------------- 1 | % tpadm ca install-ca-certificates | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **ca install-ca-certificates** - Install the Root and Intermediate Certificate Authority public keys to the Operating System trust store. 6 | 7 | # SYNOPSIS 8 | 9 | **ca install-ca-certificates** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **ca install-ca-certificates** - Installing the Certificate Authority public certificates to the Operating System trust store enables system-wide trust for any certificate issued by the Certificate Authority. 14 | 15 | # OPTIONS 16 | 17 | 18 | ## References 19 | 20 | [common options](common/options.md) collection of common options that provide 21 | information many users may expect. 22 | 23 | # NOTES 24 | 25 | This command requires root or sudo privileges. 26 | 27 | # EXAMPLES 28 | 29 | ## Install Certificate Authority certificates to local OS trust store 30 | ```bash 31 | sudo tpadm ca install-ca-certificates 32 | ``` 33 | 34 | # AUTHOR 35 | Jeremy Hahn 36 | https://github.com/jeremyhahn 37 | https://www.linkdedin.com/in/jeremyhahn 38 | 39 | # COPYRIGHT 40 | (c) 2024 Jeremy Hahn 41 | All Rights Reserved 42 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/certificate.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 9 | ) 10 | 11 | func (s *RestService) CertificateHandler(w http.ResponseWriter, r *http.Request) { 12 | 13 | s.logger.Debug("CertificateHandler", "method", r.Method, "url", r.URL) 14 | for name, values := range r.Header { 15 | for _, value := range values { 16 | s.logger.Debugf("%s: %s\n", name, value) 17 | } 18 | } 19 | 20 | strCertID := mux.Vars(r)["id"] 21 | certID, err := strconv.ParseUint(strCertID, 10, 64) 22 | if err != nil { 23 | writeError(w, acme.MalformedError("Invalid certificate ID", nil)) 24 | return 25 | } 26 | certificateDAO, err := s.params.DAOFactory.ACMECertificateDAO() 27 | if err != nil { 28 | writeError(w, acme.ServerInternal("Failed to create certificate DAO")) 29 | return 30 | } 31 | certificate, err := certificateDAO.Get(certID, s.consistencyLevel) 32 | if err != nil { 33 | writeError(w, acme.MalformedError("Certificate not found", nil)) 34 | return 35 | } 36 | 37 | w.Header().Set("Content-Type", "application/pem-certificate-chain") 38 | w.WriteHeader(http.StatusOK) 39 | w.Write([]byte(certificate.PEM)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/tpm2/encoding_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEncodeDecodeQuote(t *testing.T) { 10 | 11 | _, tpm := createSim(false, false) 12 | defer tpm.Close() 13 | 14 | nonce := []byte("nonce") 15 | 16 | pcrs := []uint{0, 1, 2, 3} 17 | quote, err := tpm.Quote(pcrs, nonce) 18 | assert.Nil(t, err) 19 | assert.NotNil(t, quote.Quoted) 20 | 21 | encoded, err := EncodeQuote(quote) 22 | assert.Nil(t, err) 23 | assert.NotNil(t, encoded) 24 | 25 | decoded, err := DecodeQuote(encoded) 26 | assert.Nil(t, err) 27 | assert.NotNil(t, decoded) 28 | 29 | assert.Equal(t, quote, decoded) 30 | } 31 | 32 | func TestEncodeDecodePCRBanks(t *testing.T) { 33 | 34 | _, tpm := createSim(false, false) 35 | defer tpm.Close() 36 | 37 | pcrs := []uint{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 38 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23} 39 | banks, err := tpm.ReadPCRs(pcrs) 40 | assert.Nil(t, err) 41 | assert.NotNil(t, banks) 42 | 43 | encoded, err := EncodePCRs(banks) 44 | assert.Nil(t, err) 45 | assert.NotNil(t, encoded) 46 | 47 | decoded, err := DecodePCRs(encoded) 48 | assert.Nil(t, err) 49 | assert.NotNil(t, decoded) 50 | 51 | assert.Equal(t, banks, decoded) 52 | } 53 | -------------------------------------------------------------------------------- /docs/TPM-Provisioning.md: -------------------------------------------------------------------------------- 1 | # TPM Provisioning 2 | 3 | This document describes the provisioning details used by the Trusted Platform to provision a new platform. 4 | 5 | # Auhtorization Implementation 6 | 7 | The Trusted Platform uses the "less complex" implementation mentioned in Section 7.2.2 - Implementation options - from the TCG TPM 2.0 Keys for Device Identity and Attestation. 8 | 9 | An enterprise may safely configure the TPM per the "most complex" implementation option using a Delegation Policy following provisioning the platform using the TSS command line tools. 10 | 11 | Likewise, the "simplest implementation" may also be safely implemented by the Platform Administrator or Owner following provisioning by using the same TSS command line tools. 12 | 13 | # References 14 | 15 | https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf 16 | 17 | https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf 18 | 19 | https://trustedcomputinggroup.org/wp-content/uploads/TCG-OID-Registry-Version-1.00-Revision-0.74_10July24.pdf 20 | 21 | https://reference.opcfoundation.org/Onboarding/v105/docs/5.1 22 | 23 | https://datatracker.ietf.org/doc/html/draft-acme-device-attest-03 -------------------------------------------------------------------------------- /docs/man/common/options.md: -------------------------------------------------------------------------------- 1 | This is the set of global options supported by every command in the CLI: 2 | 3 | * **-h**, **\--help**: 4 | Display the commands usage. The help menu is also available for subcommands. 5 | 6 | * **-d**, **\--debug**: 7 | Enables debug mode with verbose logging 8 | 9 | * , **\--debug-secrets**: 10 | Enables secret debugging. Passwords and other sensitive information is 11 | printed to the log for debugging. 12 | 13 | * **\--init**: 14 | Perform initialization 15 | 16 | * **\--so-pin**: 17 | The Security Officer PIN 18 | 19 | * **\--pin**: 20 | The user PIN 21 | 22 | * , **\--platform-dir**: 23 | The directory where all platform data is stored. 24 | 25 | * , **\--config-dir**: 26 | The directory where all platform configuration files are stored. 27 | 28 | * , **\--log-dir**: 29 | The directory where all platform log files are stored. 30 | 31 | * , **\--ca-dir**: 32 | The directory where all Certificate Authority files are kept. 33 | 34 | * , **\--setuid**: 35 | Sets the uid for the platform to run. If the platform is started as root, 36 | permissions are downgraded to the specified uid. 37 | 38 | * , **\--listen**: 39 | The socket listen address used for embedded network services 40 | -------------------------------------------------------------------------------- /docs/man/pkcs11.md: -------------------------------------------------------------------------------- 1 | % tpadm pkcs11 | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **pkcs11** - Perform PKCS #11 token operations 6 | 7 | # SYNOPSIS 8 | 9 | **pkcs11** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **pkcs11** - Shows library and hardware information about connected PKCS #11 14 | Hardware Security Modules. 15 | 16 | 17 | # OPTIONS 18 | 19 | * **-m**, **\--module**: 20 | 21 | The PKCS #11 module path 22 | 23 | * **-l**, **\--label**: 24 | 25 | The PKCS #11 token label, used to uniquely identify the hardware module. 26 | 27 | * **-s**, **\--slot**: 28 | 29 | The PKCS #11 token slot 30 | 31 | * **\--ykcs11**: 32 | 33 | Use the Yubico module located at /usr/local/lib/libykcs11,so 34 | 35 | * **\--softhsm2**: 36 | 37 | Use the SoftHSM2 module located at /usr/local/lib/libsofthsm2,so 38 | 39 | * **\--opensc**: 40 | 41 | Use the OpenSC module located at /usr/local/lib/opensc-pkcs11,so 42 | 43 | 44 | ## References 45 | 46 | [common options](common/options.md) collection of common options that provide 47 | information many users may expect. 48 | 49 | # NOTES 50 | 51 | 52 | # EXAMPLES 53 | 54 | ## Display SoftHSM2 module information 55 | ```bash 56 | tpadm pkcs11 --module /usr/local/lib/libsofthsm2.so 57 | ``` 58 | -------------------------------------------------------------------------------- /examples/tss/attestor/pkg/stats.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "google.golang.org/grpc/peer" 8 | "google.golang.org/grpc/stats" 9 | ) 10 | 11 | type handler struct { 12 | logger *logging.Logger 13 | attestor Attestor 14 | secureService *SecureAttestor 15 | } 16 | 17 | func (h *handler) TagRPC(ctx context.Context, stats *stats.RPCTagInfo) context.Context { 18 | return ctx 19 | } 20 | 21 | func (h *handler) HandleRPC(ctx context.Context, stats stats.RPCStats) { 22 | } 23 | 24 | func (h *handler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context { 25 | return ctx 26 | } 27 | 28 | func (h *handler) HandleConn(ctx context.Context, s stats.ConnStats) { 29 | switch s.(type) { 30 | case *stats.ConnBegin: 31 | p, _ := peer.FromContext(ctx) 32 | verifierIP := parseVerifierIP(p.Addr) 33 | h.logger.Debugf("stats-handler: accepting new verifier connection: %s", verifierIP) 34 | h.secureService.OnConnect() 35 | 36 | case *stats.ConnEnd: 37 | p, _ := peer.FromContext(ctx) 38 | verifierIP := parseVerifierIP(p.Addr) 39 | h.logger.Debugf("stats-handler: verifier terminated the connection: %s", verifierIP) 40 | h.secureService.Close(ctx, nil) 41 | ctx.Done() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | # Platform Authentication 2 | 3 | Trusted Platform Security Officer's log in via the Security Officer PIN. 4 | 5 | Trusted Platform Administrators and/or Users perform authentication via 6 | user PIN or WebAuthn. 7 | 8 | Trusted Platform web service consumers authenticate using WebAuthn and 9 | JSON Web Tokens. 10 | 11 | 12 | #### PKCS #11 13 | 14 | Use the platform configuration file to specify the location to the shared object 15 | library provided by your manufacturer's Harware Security Module. The following is 16 | an example using SoftHSM: 17 | 18 | # The path to the library 19 | library: /usr/local/lib/softhsm/libsofthsm2.so 20 | 21 | # The library configuration file 22 | config: ../../libs/configs/platform/softhsm.conf 23 | 24 | # The slot where the token is present 25 | slot: 0 26 | 27 | # The platform token label 28 | label: Trusted Platform 29 | 30 | # The Security Officer PIN 31 | # This should never be set for anything other than 32 | # development and testing. 33 | so-pin: 1234 34 | 35 | # The User PIN 36 | # This should never be set for anything other than 37 | # development and testing. 38 | pin: 5678 39 | 40 | # Seal the PIN to the TPM using the platform PCR policy 41 | platform-policy: true 42 | -------------------------------------------------------------------------------- /pkg/acme/entities/order.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type ACMEOrder struct { 4 | ID uint64 `yaml:"id" json:"id"` 5 | Status string `yaml:"status" json:"status"` 6 | Expires string `yaml:"expires" json:"expires,omitempty"` 7 | Identifiers []ACMEIdentifier `yaml:"identifiers" json:"identifiers"` 8 | NotBefore string `yaml:"not-before" json:"notBefore,omitempty"` 9 | NotAfter string `yaml:"not-after" json:"notAfter,omitempty"` 10 | Error *Error `yaml:"error" json:"error,omitempty"` 11 | Authorizations []string `yaml:"authorizations" json:"authorizations"` 12 | Finalize string `yaml:"finalize" json:"finalize"` 13 | Certificate string `yaml:"certificate" json:"certificate,omitempty"` 14 | CertificateURL string `yaml:"certificate-url" json:"certificate_url,omitempty"` 15 | XSignedOrder *ACMEOrder `yaml:"x-signed-order" json:"x-signed-order,omitempty"` 16 | AccountID uint64 `yaml:"account-id" json:"account_id"` 17 | URL string `yaml:"url" json:"url"` 18 | } 19 | 20 | func (order *ACMEOrder) SetEntityID(id uint64) { 21 | order.ID = id 22 | } 23 | 24 | func (order *ACMEOrder) EntityID() uint64 { 25 | return order.ID 26 | } 27 | -------------------------------------------------------------------------------- /pkg/store/keystore/password.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | // A secret is a private piece of information that unlocks 4 | // protected data or resources, and can include passwords, 5 | // but also other types of sensitive data. Passwords can be 6 | // used for a variety of purposes, including system-to-system 7 | // interactions, such as when a web application communicates 8 | // with a database. 9 | 10 | // Passwords are a type of secret that are often used to access 11 | // computer systems or enter a place, such as a protected vault. 12 | type Password interface { 13 | String() (string, error) 14 | Bytes() ([]byte, error) 15 | } 16 | 17 | type ClearPassword struct { 18 | password []byte 19 | Password 20 | } 21 | 22 | // Creates a new clear text password stored in memory 23 | func NewClearPassword(password []byte) Password { 24 | return ClearPassword{password: password} 25 | } 26 | 27 | // Creates a new clear text password stored in memory from a string 28 | func NewClearPasswordFromString(password string) Password { 29 | return ClearPassword{password: []byte(password)} 30 | } 31 | 32 | // Returns the password as a string 33 | func (p ClearPassword) String() (string, error) { 34 | return string(p.password), nil 35 | } 36 | 37 | // Returns the password as bytes 38 | func (p ClearPassword) Bytes() ([]byte, error) { 39 | return p.password, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/cmd/ca/init.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/prompt" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var InitCmd = &cobra.Command{ 11 | Use: "init", 12 | Short: "Initialize the Certificate Authority", 13 | Long: `Initializes the Certificate Authority by creating a Root and 14 | Intermediates as specified in the platform configuration file.`, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | 17 | prompt.PrintBanner(app.Version) 18 | 19 | App, err = App.Init(InitParams) 20 | if err != nil { 21 | cmd.PrintErrln(err) 22 | return 23 | } 24 | 25 | var soPIN, userPIN keystore.Password 26 | if App.CA == nil { 27 | soPIN, userPIN, err = App.ParsePINs(InitParams.SOPin, InitParams.Pin) 28 | if err != nil { 29 | App.Logger.Error(err) 30 | cmd.PrintErrln(err) 31 | return 32 | } 33 | if err := App.LoadCA(soPIN, userPIN); err != nil { 34 | App.Logger.Error(err) 35 | cmd.PrintErrln(err) 36 | return 37 | } 38 | } 39 | 40 | if _, err := App.InitCA(soPIN, userPIN, InitParams); err != nil { 41 | cmd.PrintErrln(err) 42 | return 43 | } 44 | 45 | cmd.Println("Certificate Authority successfully initialized") 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /pkg/tpm2/provision_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestProvisionOwner(t *testing.T) { 11 | 12 | logger, tpm := createSim(false, false) 13 | defer tpm.Close() 14 | 15 | ekAttrs, err := tpm.EKAttributes() 16 | assert.Nil(t, err) 17 | 18 | ssrkAttrs, err := tpm.SSRKAttributes() 19 | assert.Nil(t, err) 20 | 21 | iakAttrs, err := tpm.IAKAttributes() 22 | assert.Nil(t, err) 23 | 24 | keystore.DebugKeyAttributes(logger, ekAttrs) 25 | keystore.DebugKeyAttributes(logger, ssrkAttrs) 26 | keystore.DebugKeyAttributes(logger, iakAttrs) 27 | } 28 | 29 | func TestSetHierarchyAuth(t *testing.T) { 30 | 31 | _, tpm := createSim(false, false) 32 | defer tpm.Close() 33 | 34 | ekAttrs, err := tpm.EKAttributes() 35 | assert.Nil(t, err) 36 | 37 | hierarchyAuth := ekAttrs.TPMAttributes.HierarchyAuth 38 | 39 | password := keystore.NewClearPassword([]byte("test")) 40 | err = tpm.SetHierarchyAuth(hierarchyAuth, password, nil) 41 | 42 | assert.Nil(t, err) 43 | } 44 | 45 | func TestGoldenMeasurements(t *testing.T) { 46 | 47 | logger, tpm := createSim(false, false) 48 | defer tpm.Close() 49 | 50 | goldenPCR := tpm.GoldenMeasurements() 51 | assert.NotNil(t, goldenPCR) 52 | 53 | logger.Debugf("Golden PCR: 0x%s", Encode(goldenPCR)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/platform/prompt/prompt.go: -------------------------------------------------------------------------------- 1 | package prompt 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "syscall" 9 | 10 | "github.com/fatih/color" 11 | "golang.org/x/term" 12 | ) 13 | 14 | const ( 15 | userPrompt = "trusted-platform> $ " 16 | ) 17 | 18 | func PrintBanner(version string) { 19 | color.New(color.FgGreen).Printf("Trusted Platform v%s\n\n", version) 20 | } 21 | 22 | func PasswordPrompt(message string) []byte { 23 | fmt.Printf("%s: \n", message) 24 | fmt.Printf(userPrompt) 25 | sopin, err := term.ReadPassword(int(syscall.Stdin)) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | fmt.Println() 30 | return sopin 31 | } 32 | 33 | func SOPin() []byte { 34 | return PasswordPrompt("Security Officer PIN") 35 | } 36 | 37 | func Pin() []byte { 38 | return PasswordPrompt("User PIN") 39 | } 40 | 41 | func KeyPassword() []byte { 42 | return PasswordPrompt("Key Password") 43 | } 44 | 45 | func Prompt(message string) []byte { 46 | reader := bufio.NewReader(os.Stdin) 47 | 48 | fmt.Printf("%s: \n", message) 49 | fmt.Printf(userPrompt) 50 | 51 | response, err := reader.ReadString('\n') 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | return []byte(response) 57 | } 58 | 59 | func NoOpPrompt() []byte { 60 | reader := bufio.NewReader(os.Stdin) 61 | response, err := reader.ReadString('\n') 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | return []byte(response) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/platform/service/role.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 9 | ) 10 | 11 | var ( 12 | ErrRoleNotFound = errors.New("role not found") 13 | ) 14 | 15 | type RoleServicer interface { 16 | GetPage(pageQuery datastore.PageQuery) (datastore.PageResult[*entities.Role], error) 17 | GetByName(name string, CONSISTENCY_LEVEL datastore.ConsistencyLevel) (*entities.Role, error) 18 | } 19 | 20 | type RoleService struct { 21 | logger *logging.Logger 22 | roleDAO datastore.RoleDAO 23 | RoleServicer 24 | } 25 | 26 | func NewRoleService( 27 | logger *logging.Logger, 28 | roleDAO datastore.RoleDAO) RoleServicer { 29 | 30 | return &RoleService{ 31 | logger: logger, 32 | roleDAO: roleDAO} 33 | } 34 | 35 | // Returns a single page of role entities from the database 36 | func (service *RoleService) Page(pageQuery datastore.PageQuery) (datastore.PageResult[*entities.Role], error) { 37 | return service.roleDAO.Page(pageQuery, datastore.ConsistencyLevelLocal) 38 | } 39 | 40 | // Returns the role with the given name 41 | func (service *RoleService) GetByName(name string, CONSISTENCY_LEVEL datastore.ConsistencyLevel) (*entities.Role, error) { 42 | return service.roleDAO.GetByName(name, CONSISTENCY_LEVEL) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/tpm2/tpm_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // Create a fake TPM cert 11 | // https://gist.github.com/op-ct/e202fc911de22c018effdb3371e8335f 12 | // https://github.com/osresearch/safeboot/pull/85 13 | 14 | /* 15 | // Verify CA chain: 16 | 17 | openssl verify \ 18 | -CAfile testdata/root-ca/root-ca.crt \ 19 | testdata/intermediate-ca/intermediate-ca.crt 20 | 21 | // Verify CA chain & server certificate: 22 | 23 | openssl verify \ 24 | -CAfile testdata/intermediate-ca/trusted-root/root-ca.crt \ 25 | -untrusted testdata/intermediate-ca/intermediate-ca.crt \ 26 | testdata/intermediate-ca/issued/localhost/localhost.crt 27 | 28 | // Verify EK chain & certificate: 29 | 30 | openssl verify \ 31 | -CAfile testdata/intermediate-ca/trusted-root/www.intel.com.crt \ 32 | -untrusted testdata/intermediate-ca/trusted-intermediate/CNLEPIDPOSTB1LPPROD2_EK_Platform_Public_Key.crt \ 33 | testdata/intermediate-ca/issued/tpm-ek/tpm-ek.crt 34 | */ 35 | 36 | func TestInfo(t *testing.T) { 37 | 38 | _, tpm := createSim(false, false) 39 | defer tpm.Close() 40 | 41 | props, err := tpm.FixedProperties() 42 | assert.Nil(t, err) 43 | assert.NotNil(t, props.Manufacturer) 44 | assert.NotNil(t, props.VendorID) 45 | assert.NotNil(t, props.Family) 46 | assert.NotNil(t, props.FwMajor) 47 | assert.NotNil(t, props.FwMinor) 48 | 49 | fmt.Println(props) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/webservice/mtls_signer.go: -------------------------------------------------------------------------------- 1 | package webservice 2 | 3 | import ( 4 | "crypto" 5 | "crypto/x509" 6 | "io" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/ca" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 10 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 11 | ) 12 | 13 | type Signer struct { 14 | logger *logging.Logger 15 | ca ca.CertificateAuthority 16 | cert *x509.Certificate 17 | cn string 18 | password []byte 19 | crypto.Signer 20 | } 21 | 22 | func NewSigner( 23 | logger *logging.Logger, 24 | ca ca.CertificateAuthority, 25 | cert *x509.Certificate, 26 | cn string, 27 | password []byte) *Signer { 28 | 29 | return &Signer{ 30 | logger: logger, 31 | ca: ca, 32 | cert: cert, 33 | cn: cn, 34 | password: password} 35 | } 36 | 37 | func (signer *Signer) Public() crypto.PublicKey { 38 | return signer.cert.PublicKey 39 | } 40 | 41 | func (signer *Signer) Sign( 42 | rand io.Reader, 43 | digest []byte, 44 | opts crypto.SignerOpts) ([]byte, error) { 45 | 46 | signer.logger.Info("webserivce: signing digest: %s", digest) 47 | 48 | attrs := &keystore.KeyAttributes{ 49 | CN: signer.cert.Subject.CommonName, 50 | Password: keystore.NewClearPassword(signer.password), 51 | } 52 | 53 | s, err := signer.ca.Signer(attrs) 54 | if err != nil { 55 | signer.logger.Error(err) 56 | return nil, err 57 | } 58 | 59 | return s.Sign(rand, digest, opts) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/acme/challenge/endorse01/endorse01.go: -------------------------------------------------------------------------------- 1 | package endorse01 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/ca" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/serializer" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 10 | ) 11 | 12 | // Implements acme.ChallengeVerifierFunc 13 | func Verify( 14 | resolver *net.Resolver, 15 | ca ca.CertificateAuthority, 16 | domain, port, challengeToken, expectedKeyAuth string) error { 17 | 18 | // TODO: Add support for ACME EAB to authenticate endorse-01 requests. 19 | return nil 20 | } 21 | 22 | func Setup( 23 | challengeToken string, 24 | certAuthority ca.CertificateAuthority, 25 | tpm tpm2.TrustedPlatformModule) ([]byte, error) { 26 | 27 | ekPubKey := tpm.EK() 28 | ekAttrs, err := tpm.EKAttributes() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | serializer, err := keystore.NewSerializer(serializer.SERIALIZER_JSON) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | ekPubKeyBytes, err := serializer.Serialize(ekPubKey) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | csr, err := certAuthority.CreateCSR(ca.CertificateRequest{ 44 | PermanentID: string(ekPubKeyBytes), 45 | KeyAttributes: ekAttrs, 46 | Subject: ca.Subject{ 47 | CommonName: ekAttrs.CN, 48 | }, 49 | }) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return csr, nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/ca/types.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 7 | ) 8 | 9 | var ( 10 | ErrInvalidIssuingURL = errors.New("certificate-authority: invalid issuing URL") 11 | ErrUnsealFailure = errors.New("certificate-authority: key unseal operation failed") 12 | 13 | InfoUsingDefaultCAKey = errors.New("certificate-authority: no matching key algorithm, using default CA key") 14 | 15 | ATTEST_BLOB_ROOT string = "tpm2" 16 | ATTEST_BLOB_QUOTE = "quote" 17 | ATTEST_BLOB_EVENTLOG = "eventlog" 18 | ATTEST_BLOB_PCRS = "pcrs" 19 | ) 20 | 21 | type CertificateRequest struct { 22 | PermanentID string `yaml:"permanent-id" json:"permanent_id" mapstructure:"permanent-id"` 23 | ProdModel string `yaml:"prod-model" json:"prod_model" mapstructure:"prod-model"` 24 | ProdSerial string `yaml:"prod-serial" json:"prod_serial" mapstructure:"prod-serial"` 25 | SANS *SubjectAlternativeNames `yaml:"sans" json:"sans" mapstructure:"sans"` 26 | Subject Subject `yaml:"subject" json:"subject" mapstructure:"subject"` 27 | Valid int `yaml:"valid" json:"valid" mapstructure:"valid"` 28 | KeyAttributes *keystore.KeyAttributes `yaml:"-" json:"-" mapstructure:"-"` 29 | } 30 | 31 | type OSTrustStore interface { 32 | Install(cn string) error 33 | Uninstall(cn string) error 34 | } 35 | -------------------------------------------------------------------------------- /pkg/store/certstore/base_test.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | TEST_DATA_DIR = "./testdata" 10 | TEST_CN = "exapmle.com" 11 | TEST_TMP_DIR = "" 12 | TEST_RETAIN_REVOKED = true 13 | 14 | // https://phpseclib.com/docs/rsa-keys 15 | // https://fm4dd.com/openssl/certexamples.shtm 16 | TEST_CERT_PEM = []byte(`-----BEGIN CERTIFICATE----- 17 | MIICEjCCAXsCAg36MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG 18 | A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE 19 | MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl 20 | YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw 21 | ODIyMDUyNjU0WhcNMTcwODIxMDUyNjU0WjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE 22 | CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs 23 | ZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAm/xmkHmEQrurE/0re/jeFRLl 24 | 8ZPjBop7uLHhnia7lQG/5zDtZIUC3RVpqDSwBuw/NTweGyuP+o8AG98HxqxTBwID 25 | AQABMA0GCSqGSIb3DQEBBQUAA4GBABS2TLuBeTPmcaTaUW/LCB2NYOy8GMdzR1mx 26 | 8iBIu2H6/E2tiY3RIevV2OW61qY2/XRQg7YPxx3ffeUugX9F4J/iPnnu1zAxxyBy 27 | 2VguKv4SWjRFoRkIfIlHX0qVviMhSlNy2ioFLy7JcPZb+v3ftDGywUqcBiVDoea0 28 | Hn+GmxZA 29 | -----END CERTIFICATE-----`) 30 | ) 31 | 32 | func TestMain(m *testing.M) { 33 | setup() 34 | code := m.Run() 35 | teardown() 36 | os.Exit(code) 37 | } 38 | 39 | func teardown() { 40 | // os.RemoveAll(TEST_DATA_DIR) 41 | } 42 | 43 | func setup() { 44 | os.RemoveAll(TEST_DATA_DIR) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/ca/encoding.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509" 6 | "encoding/pem" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 9 | ) 10 | 11 | // Encodes a raw DER byte array as a PEM byte array 12 | func EncodePEM(derCert []byte) ([]byte, error) { 13 | caPEM := new(bytes.Buffer) 14 | err := pem.Encode(caPEM, &pem.Block{ 15 | Type: "CERTIFICATE", 16 | Bytes: derCert, 17 | }) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return caPEM.Bytes(), nil 22 | } 23 | 24 | // Decodes PEM bytes to *x509.Certificate 25 | func DecodePEM(bytes []byte) (*x509.Certificate, error) { 26 | var block *pem.Block 27 | if block, _ = pem.Decode(bytes); block == nil { 28 | return nil, keystore.ErrInvalidEncodingPEM 29 | } 30 | return x509.ParseCertificate(block.Bytes) 31 | } 32 | 33 | // Encodes a Certificate Signing Request to PEM form 34 | func EncodeCSR(csr []byte) ([]byte, error) { 35 | csrPEM := new(bytes.Buffer) 36 | csrBlock := &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr} 37 | if err := pem.Encode(csrPEM, csrBlock); err != nil { 38 | return nil, err 39 | } 40 | return csrPEM.Bytes(), nil 41 | } 42 | 43 | // Decodes CSR bytes to x509.CertificateRequest 44 | func DecodeCSR(csrPEM []byte) (*x509.CertificateRequest, error) { 45 | var block *pem.Block 46 | if block, _ = pem.Decode(csrPEM); block == nil { 47 | return nil, keystore.ErrInvalidEncodingPEM 48 | } 49 | return x509.ParseCertificateRequest(block.Bytes) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/device/service.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/dao" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/device/entities" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 9 | ) 10 | 11 | var ( 12 | ErrInvalidDeviceID = fmt.Errorf("invalid device ID") 13 | ) 14 | 15 | type Service struct { 16 | consistencyLevel datastore.ConsistencyLevel 17 | deviceDAO dao.DeviceDAO 18 | } 19 | 20 | func NewService(config *Config) (*Service, error) { 21 | 22 | dsParams, err := datastore.ParamsFromConfig[*entities.Device]( 23 | config.Datastore, DatastorePartition) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | storeType, err := datastore.ParseStoreType(config.Datastore.Backend) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | ds := NewDatastore(dsParams, storeType) 34 | 35 | deviceDAO, err := ds.DeviceDAO() 36 | if err != nil { 37 | return nil, fmt.Errorf("failed to get device DAO: %w", err) 38 | } 39 | 40 | return &Service{ 41 | consistencyLevel: datastore.ParseConsistentLevel(config.Datastore.ConsistencyLevel), 42 | deviceDAO: deviceDAO, 43 | }, nil 44 | } 45 | 46 | func (s *Service) Save(device *entities.Device) error { 47 | 48 | if device.ID == 0 { 49 | return ErrInvalidDeviceID 50 | } 51 | 52 | if err := s.deviceDAO.Save(device); err != nil { 53 | return fmt.Errorf("failed to save device: %w", err) 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /docs/man/tpm/seal.md: -------------------------------------------------------------------------------- 1 | % tpadm tpm seal | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **tpm seal** - Retrieves a sealed seal from the tpm TPM store. 6 | 7 | # SYNOPSIS 8 | 9 | **tpm seal** [*ARGUMENT*] [*OPTIONS*] 10 | 11 | # DESCRIPTION 12 | 13 | **tpm seal** - Retrieves a sealed seal from the tpm TPM 2.0 key store. 14 | 15 | 16 | # OPTIONS 17 | 18 | * **\--cn**: 19 | 20 | The key common name 21 | 22 | * **\--store**: 23 | 24 | The key store module to query for the key [ pkcs8 | pkcs11 | tpm2 ] 25 | 26 | * **\--algorithm**: 27 | 28 | The key algorithm [ rsa | ecdsa | ed25519 ] 29 | 30 | * **\--auth**: 31 | 32 | The parent key seal authorization value 33 | 34 | * **\--policy**: 35 | 36 | When true, the a PCR policy session is used to access the sealed seal 37 | 38 | 39 | ## References 40 | 41 | [common options](common/options.md) collection of common options that provide 42 | information many users may expect. 43 | 44 | # NOTES 45 | 46 | 47 | # EXAMPLES 48 | 49 | ## Retrieve the default Root CA seal (PKCS8, RSA) 50 | ```bash 51 | tpadm tpm seal --cn root-ca.example.com 52 | ``` 53 | 54 | ## Retrieve the Root CA seal using provided store and key algorithm 55 | ```bash 56 | tpadm tpm seal --cn root-ca.example.com --algorithm ecdsa --store pkcs11 57 | ``` 58 | 59 | # AUTHOR 60 | Jeremy Hahn 61 | https://github.com/jeremyhahn 62 | https://www.linkdedin.com/in/jeremyhahn 63 | 64 | # COPYRIGHT 65 | (c) 2024 Jeremy Hahn 66 | All Rights Reserved 67 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/order.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 10 | ) 11 | 12 | const ( 13 | acme_order_partition = "acme/%d/orders" 14 | ) 15 | 16 | type ACMEOrderDAO struct { 17 | accountID uint64 18 | params *datastore.Params[*entities.ACMEOrder] 19 | *kvstore.AferoDAO[*entities.ACMEOrder] 20 | } 21 | 22 | func NewACMEOrderDAO( 23 | params *datastore.Params[*entities.ACMEOrder], 24 | accountID uint64) (dao.ACMEOrderDAO, error) { 25 | 26 | params.Partition = fmt.Sprintf("acme/%d/orders", accountID) 27 | aferoDAO, err := kvstore.NewAferoDAO(params) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &ACMEOrderDAO{ 32 | accountID: accountID, 33 | AferoDAO: aferoDAO, 34 | params: params, 35 | }, nil 36 | } 37 | 38 | func (orderDAO *ACMEOrderDAO) GetByAccountID(CONSISTENCY_LEVEL datastore.ConsistencyLevel) (datastore.PageResult[*entities.ACMEOrder], error) { 39 | 40 | params := *orderDAO.params 41 | params.Partition = fmt.Sprintf(acme_order_partition, orderDAO.accountID) 42 | aferoDAO, err := kvstore.NewAferoDAO(¶ms) 43 | if err != nil { 44 | return datastore.PageResult[*entities.ACMEOrder]{}, err 45 | } 46 | pageQuery := datastore.NewPageQuery() 47 | 48 | return aferoDAO.Page(pageQuery, CONSISTENCY_LEVEL) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/handler_order.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 9 | ) 10 | 11 | func (s *RestService) OrderHandler(w http.ResponseWriter, r *http.Request) { 12 | 13 | s.logger.Debug("OrderHandler", "method", r.Method, "url", r.URL) 14 | for name, values := range r.Header { 15 | for _, value := range values { 16 | s.logger.Debugf("%s: %s\n", name, value) 17 | } 18 | } 19 | 20 | account, _, err := s.parseKID(r) 21 | if err != nil { 22 | writeError(w, acme.MalformedError("Failed to parse account key", nil)) 23 | return 24 | } 25 | 26 | if r.Method != http.MethodPost { 27 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 28 | return 29 | } 30 | 31 | strOrderID := mux.Vars(r)["id"] 32 | orderID, err := strconv.ParseUint(strOrderID, 10, 64) 33 | if err != nil { 34 | writeError(w, acme.MalformedError("Invalid order ID", nil)) 35 | return 36 | } 37 | 38 | orderDAO, err := s.params.DAOFactory.ACMEOrderDAO(account.ID) 39 | if err != nil { 40 | writeError(w, acme.ServerInternal("Failed to create order DAO")) 41 | return 42 | } 43 | 44 | order, err := orderDAO.Get(orderID, s.consistencyLevel) 45 | if err != nil { 46 | writeError(w, acme.MalformedError("Order not found", nil)) 47 | return 48 | } 49 | 50 | if order.AccountID != account.ID { 51 | writeError(w, acme.Unauthorized("Unauthorized")) 52 | return 53 | } 54 | 55 | s.orderResponse(w, order) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/acme/dao/types.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/serializer" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | ) 8 | 9 | type ACMEAccountDAO interface { 10 | datastore.GenericDAO[*entities.ACMEAccount] 11 | } 12 | 13 | type ACMEAuthorizationDAO interface { 14 | datastore.GenericDAO[*entities.ACMEAuthorization] 15 | } 16 | 17 | type ACMECertificateDAO interface { 18 | datastore.GenericDAO[*entities.ACMECertificate] 19 | } 20 | 21 | type ACMEChallengeDAO interface { 22 | datastore.GenericDAO[*entities.ACMEChallenge] 23 | } 24 | 25 | type ACMEOrderDAO interface { 26 | GetByAccountID( 27 | CONSISTENCY_LEVEL datastore.ConsistencyLevel) (datastore.PageResult[*entities.ACMEOrder], error) 28 | datastore.GenericDAO[*entities.ACMEOrder] 29 | } 30 | 31 | type ACMEIdentifierDAO interface { 32 | datastore.GenericDAO[*entities.ACMEIdentifier] 33 | } 34 | 35 | type ACMENonceDAO interface { 36 | datastore.GenericDAO[*entities.ACMENonce] 37 | } 38 | 39 | type Factory interface { 40 | ACMEAccountDAO() (ACMEAccountDAO, error) 41 | ACMEAuthorizationDAO(accountID uint64) (ACMEAuthorizationDAO, error) 42 | ACMECertificateDAO() (ACMECertificateDAO, error) 43 | ACMEChallengeDAO(accountID uint64) (ACMEChallengeDAO, error) 44 | ACMEOrderDAO(accountID uint64) (ACMEOrderDAO, error) 45 | ACMENonceDAO() (ACMENonceDAO, error) 46 | SerializerType() serializer.SerializerType 47 | ConsistencyLevel() datastore.ConsistencyLevel 48 | } 49 | -------------------------------------------------------------------------------- /pkg/store/keystore/dilithium2/dilithium2_test.go: -------------------------------------------------------------------------------- 1 | //go:build quantum_safe 2 | 3 | package dilithium2 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | d, err := New() 13 | defer d.Clean() 14 | assert.NoError(t, err) 15 | assert.NotNil(t, d.signer) 16 | } 17 | 18 | func TestClean(t *testing.T) { 19 | d, err := New() 20 | defer d.Clean() 21 | assert.NoError(t, err) 22 | d.Clean() 23 | assert.Nil(t, d.signer) 24 | } 25 | 26 | func TestGenerateKeyPair(t *testing.T) { 27 | d, err := New() 28 | defer d.Clean() 29 | assert.NoError(t, err) 30 | pubKey, err := d.GenerateKeyPair() 31 | assert.NoError(t, err) 32 | assert.NotNil(t, pubKey) 33 | } 34 | 35 | func TestSignAndVerify(t *testing.T) { 36 | d, err := New() 37 | defer d.Clean() 38 | assert.NoError(t, err) 39 | pubKey, err := d.GenerateKeyPair() 40 | assert.NoError(t, err) 41 | 42 | data := []byte("test data") 43 | signature, err := d.Sign(data) 44 | assert.NoError(t, err) 45 | assert.NotNil(t, signature) 46 | 47 | err = d.Verify(data, signature, pubKey) 48 | assert.NoError(t, err) 49 | } 50 | 51 | func TestVerifyInvalidSignature(t *testing.T) { 52 | d, err := New() 53 | defer d.Clean() 54 | assert.NoError(t, err) 55 | pubKey, err := d.GenerateKeyPair() 56 | assert.NoError(t, err) 57 | 58 | data := []byte("test data") 59 | invalidSignature := []byte("invalid signature") 60 | 61 | err = d.Verify(data, invalidSignature, pubKey) 62 | assert.Error(t, err) 63 | assert.Equal(t, "signature verification failed", err.Error()) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/cmd/ca/info_test.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_Info(t *testing.T) { 12 | 13 | // Part 1: provision and initialize the platform 14 | 15 | // When the EK certificate handle is 0, the cert 16 | // is loaded from the x509 store instead of TPM 17 | // NV RAM 18 | app.DefaultConfig.TPMConfig.EK.CertHandle = 0 19 | 20 | InitParams.Initialize = true 21 | InitParams.Pin = []byte("test") 22 | InitParams.SOPin = []byte("test") 23 | InitParams.Env = app.EnvTest.String() 24 | 25 | App = app.DefaultTestConfig() 26 | // Provision the TPM simulator and CA so the platform 27 | // is initialized and ready to import certificates 28 | App, err := App.Init(InitParams) 29 | assert.Nil(t, err) 30 | 31 | // Part 2: Load and test 32 | 33 | // Set initialize to false so when the command is invoked 34 | // and App.Init is called again, it performs a load instead 35 | // of re-initializing the platform. 36 | InitParams.Initialize = false 37 | 38 | // info: retrieves CA initialization and key info 39 | response := executeCommand(InfoCmd, []string{}) 40 | assert.True(t, strings.Count(response, "Key Algorithm: RSA") == 2) 41 | assert.True(t, strings.Count(response, "Key Algorithm: ECDSA") == 2) 42 | assert.True(t, strings.Count(response, "Key Algorithm: Ed25519") == 1) 43 | assert.True(t, strings.Count(response, "Store: pkcs8") == 3) 44 | assert.True(t, strings.Count(response, "Store: tpm2") == 2) 45 | 46 | App.TPM.Close() 47 | } 48 | -------------------------------------------------------------------------------- /docs/man/platform/password.md: -------------------------------------------------------------------------------- 1 | % tpadm platform password | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **platform password** - Retrieves a sealed password from the platform TPM store. 6 | 7 | # SYNOPSIS 8 | 9 | **platform password** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **platform password** - Retrieves a sealed password from the platform TPM 2.0 key store. 14 | 15 | 16 | # OPTIONS 17 | 18 | * **\--cn**: 19 | 20 | The key common name 21 | 22 | * **\--store**: 23 | 24 | The key store module to query for the key [ pkcs8 | pkcs11 | tpm2 ] 25 | 26 | * **\--algorithm**: 27 | 28 | The key algorithm [ rsa | ecdsa | ed25519 ] 29 | 30 | * **\--auth**: 31 | 32 | The parent key password authorization value 33 | 34 | * **\--policy**: 35 | 36 | When true, the a PCR policy session is used to access the sealed password 37 | 38 | 39 | ## References 40 | 41 | [common options](common/options.md) collection of common options that provide 42 | information many users may expect. 43 | 44 | # NOTES 45 | 46 | 47 | # EXAMPLES 48 | 49 | ## Retrieve the default Root CA password (PKCS8, RSA) 50 | ```bash 51 | tpadm platform password --cn root-ca.example.com 52 | ``` 53 | 54 | ## Retrieve the Root CA password using provided store and key algorithm 55 | ```bash 56 | tpadm platform password --cn root-ca.example.com --algorithm ecdsa --store pkcs11 57 | ``` 58 | 59 | # AUTHOR 60 | Jeremy Hahn 61 | https://github.com/jeremyhahn 62 | https://www.linkdedin.com/in/jeremyhahn 63 | 64 | # COPYRIGHT 65 | (c) 2024 Jeremy Hahn 66 | All Rights Reserved 67 | -------------------------------------------------------------------------------- /pkg/store/keystore/tpm2/secret.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 9 | ) 10 | 11 | type PlatformSecret struct { 12 | logger *logging.Logger 13 | backend keystore.KeyBackend 14 | tpm tpm2.TrustedPlatformModule 15 | keyAttrs *keystore.KeyAttributes 16 | keystore.Password 17 | } 18 | 19 | // TPM 2.0 AES symmetric encryption and wrapping operations 20 | func NewPlatformSecret( 21 | backend keystore.KeyBackend, 22 | tpm tpm2.TrustedPlatformModule, 23 | keyAttrs *keystore.KeyAttributes) keystore.Password { 24 | 25 | return PlatformSecret{ 26 | backend: backend, 27 | tpm: tpm, 28 | keyAttrs: keyAttrs} 29 | } 30 | 31 | // Returns the secret as a string 32 | func (p PlatformSecret) String() (string, error) { 33 | secret, err := p.Bytes() 34 | if err != nil { 35 | return "", err 36 | } 37 | return string(secret), nil 38 | } 39 | 40 | // Returns the secret as bytes 41 | func (p PlatformSecret) Bytes() ([]byte, error) { 42 | if p.keyAttrs.Debug { 43 | p.logger.Debugf( 44 | "keystore/tpm2: retrieving platform secret: %s", 45 | p.keyAttrs.CN) 46 | } 47 | // Copy the key attributes to a new "secret attributes" 48 | // object so it can be loaded from the backend using the 49 | // key type 50 | secretAttrs := *p.keyAttrs 51 | secretAttrs.CN = fmt.Sprintf("%s.secret", secretAttrs.CN) 52 | secretAttrs.KeyType = keystore.KEY_TYPE_SECRET 53 | return p.tpm.Unseal(&secretAttrs, p.backend) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/tpm2/version.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // VersionStringToInt64 converts a TPM firmware version string (major.minor) to an int64. 10 | func VersionStringToInt64(version string) (int64, error) { 11 | parts := strings.Split(version, ".") 12 | if len(parts) != 2 { 13 | return 0, fmt.Errorf("invalid version format, expected 'major.minor', got %q", version) 14 | } 15 | 16 | // Parse major component 17 | major, err := strconv.ParseInt(parts[0], 10, 32) 18 | if err != nil { 19 | return 0, fmt.Errorf("invalid major version: %v", err) 20 | } 21 | if major < 0 || major > 0xFFFF { 22 | return 0, fmt.Errorf("major version out of range (0-65535): %d", major) 23 | } 24 | 25 | // Parse minor component 26 | minor, err := strconv.ParseInt(parts[1], 10, 32) 27 | if err != nil { 28 | return 0, fmt.Errorf("invalid minor version: %v", err) 29 | } 30 | if minor < 0 || minor > 0xFFFF { 31 | return 0, fmt.Errorf("minor version out of range (0-65535): %d", minor) 32 | } 33 | 34 | // Combine major and minor into a single int64 35 | versionInt := (major << 16) | minor 36 | return versionInt, nil 37 | } 38 | 39 | // Int64ToVersionComponents converts an int64 back to major and minor version components. 40 | func Int64ToVersionComponents(versionInt int64) (int64, int64, error) { 41 | if versionInt < 0 || versionInt > 0xFFFFFFFF { 42 | return 0, 0, fmt.Errorf("version number out of range (0-4294967295): %d", versionInt) 43 | } 44 | 45 | major := (versionInt >> 16) & 0xFFFF 46 | minor := versionInt & 0xFFFF 47 | 48 | return major, minor, nil 49 | } 50 | -------------------------------------------------------------------------------- /docs/man/platform/install.md: -------------------------------------------------------------------------------- 1 | % tpadm platform install | Trusted Platform Commands Manual 2 | 3 | # NAME 4 | 5 | **platform install** - Perform safe, idempotent installation 6 | 7 | # SYNOPSIS 8 | 9 | **platform install** [*OPTIONS*] [*ARGUMENT*] 10 | 11 | # DESCRIPTION 12 | 13 | **platform install** - Install performs a safe, modified version of the TCG recommended provisioning guidance procedure intended for platforms that have already been provisioned to some extent by the TPM Manufacturer or Owner. Instead of clearing the hierarchies, setting hierarchy authorizations and provisioning new keys and certificates from scratch, this operation will use pre-existing EK, SRK, IAK and IDevID keys and certificates if they already exist. The provided Security Officer PIN is used as the new Endorsement and Storage hierarchy authorizations during installation. 14 | 15 | 16 | # OPTIONS 17 | 18 | 19 | ## References 20 | 21 | [common options](common/options.md) collection of common options that provide 22 | information many users may expect. 23 | 24 | # NOTES 25 | 26 | The current hierarchy authorization is expected to be set to an empty password. You can use the included CLI or TPM2 tools to execute the TPM2_HierarchyChangeAuth command to set the password to an empty password. 27 | 28 | This operation is idempotent. 29 | 30 | # EXAMPLES 31 | 32 | ## Perform safe installation 33 | ```bash 34 | tpadm platform install 35 | ``` 36 | 37 | # AUTHOR 38 | Jeremy Hahn 39 | https://github.com/jeremyhahn 40 | https://www.linkdedin.com/in/jeremyhahn 41 | 42 | # COPYRIGHT 43 | (c) 2024 Jeremy Hahn 44 | All Rights Reserved 45 | -------------------------------------------------------------------------------- /docs/TPM-PCRs.md: -------------------------------------------------------------------------------- 1 | # TPM Platform Configuration Registers (PCRs) 2 | 3 | ## List of PCRs 4 | 5 | https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/ 6 | 7 | ## UEFI Secure Boot Chain 8 | 9 | https://laurie0131.gitbooks.io/understanding-uefi-secure-boot-chain/content/overview.html 10 | 11 | ## Measured Boot 12 | 13 | https://bootlin.com/blog/measured-boot-with-a-tpm-2-0-in-u-boot/ 14 | 15 | 16 | ### Linux 17 | 18 | ## Microsoft 19 | 20 | PCR 0: Core root-of-trust for measurement, EFI boot and run-time services, EFI drivers embedded in system ROM, ACPI static tables, embedded SMM code, and BIOS code 21 | PCR 1: Platform and motherboard configuration and data. Handoff tables and EFI variables that affect system configuration 22 | PCR 2: Option ROM code 23 | PCR 3: Option ROM data and configuration 24 | PCR 4: Master boot record (MBR) code or code from other boot devices 25 | PCR 5: Master boot record (MBR) partition table. Various EFI variables and the GPT table 26 | PCR 6: State transition and wake events 27 | PCR 7: Computer manufacturer-specific 28 | PCR 8: NTFS boot sector 29 | PCR 9: NTFS boot block 30 | PCR 10: Boot manager 31 | PCR 11: BitLocker access control 32 | 33 | ## Secure Boot vs Trusted Boot 34 | 35 | In trusted boot, hashing is used to measure changes at each step of the critical 36 | boot process, whereas in secure boot, firmwares are digitally signed and verified. 37 | 38 | Secure boot is generally configured along with trusted boot. 39 | 40 | ## Disk Encryption 41 | 42 | https://tpm2-software.github.io/2020/04/13/Disk-Encryption.html 43 | 44 | https://github.com/salrashid123/tpm2/blob/master/luks/README.md -------------------------------------------------------------------------------- /pkg/dns/tld_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestLoadTLDs(t *testing.T) { 15 | mockData := `# This is a comment 16 | com 17 | ORG 18 | net 19 | # Another comment 20 | edu 21 | gov 22 | ` 23 | 24 | // Create a mock HTTP server to simulate the TLD source 25 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | fmt.Fprintln(w, mockData) 27 | })) 28 | defer server.Close() 29 | 30 | logger := &logging.Logger{} 31 | 32 | err := LoadTLDs(logger, []byte(mockData)) 33 | require.NoError(t, err, "LoadTLDs should not return an error") 34 | 35 | // Test the loaded TLDs 36 | expectedTLDs := map[string]struct{}{ 37 | "com": {}, 38 | "org": {}, 39 | "net": {}, 40 | "edu": {}, 41 | "gov": {}, 42 | } 43 | 44 | // Ensure all TLDs are loaded correctly 45 | for tld := range expectedTLDs { 46 | assert.True(t, IsTLD(tld), "Expected TLD '%s' to be in the set", tld) 47 | } 48 | 49 | // Test non-existing TLDs 50 | nonExistentTLDs := []string{"xyz", "example", "invalid"} 51 | for _, tld := range nonExistentTLDs { 52 | assert.False(t, IsTLD(tld), "Did not expect TLD '%s' to be in the set", tld) 53 | } 54 | 55 | // Test case insensitivity 56 | caseInsensitiveTLDs := []string{"COM", "Org", "NeT"} 57 | for _, tld := range caseInsensitiveTLDs { 58 | assert.True(t, IsTLD(tld), "Expected TLD '%s' to be in the set (case-insensitive)", tld) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/store/datastore/entities/aggregate_root.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | ErrEmptyReferencesSlice = errors.New("entity/aggregate-root: empty references slice") 10 | ) 11 | 12 | // Aggretate root used to manage key/value entity relationships 13 | type AggregateRoot struct { 14 | References []KeyValueEntity `yaml:"-" json:"-"` 15 | ReferencePartition string `yaml:"-" json:"-"` 16 | Root KeyValueEntity `yaml:"-" json:"-"` 17 | RootPartition string `yaml:"-" json:"-"` 18 | } 19 | 20 | // Create a new key/value aggregate root 21 | func NewAggregateRoot( 22 | root KeyValueEntity, 23 | rootPartition string, 24 | references []KeyValueEntity, 25 | referencesPartition string) (*AggregateRoot, error) { 26 | 27 | if len(references) <= 0 { 28 | return nil, ErrEmptyReferencesSlice 29 | } 30 | return &AggregateRoot{ 31 | Root: root, 32 | RootPartition: rootPartition, 33 | References: references, 34 | ReferencePartition: referencesPartition, 35 | }, nil 36 | } 37 | 38 | // Returns the aggregate root partition 39 | func (aggregate *AggregateRoot) Partition() string { 40 | var sb strings.Builder 41 | sb.WriteString(aggregate.RootPartition) 42 | sb.WriteString("_") 43 | sb.WriteString(aggregate.ReferencePartition) 44 | return sb.String() 45 | } 46 | 47 | // Returns a list of the referenced entity ids 48 | func (aggregate *AggregateRoot) EntityIDs() []uint64 { 49 | ids := make([]uint64, len(aggregate.References)) 50 | for i, ref := range aggregate.References { 51 | ids[i] = ref.EntityID() 52 | } 53 | return ids 54 | } 55 | -------------------------------------------------------------------------------- /pkg/logging/error.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log/slog" 5 | "path/filepath" 6 | 7 | "github.com/mdobak/go-xerrors" 8 | ) 9 | 10 | type stackFrame struct { 11 | Func string `json:"func"` 12 | Source string `json:"source"` 13 | Line int `json:"line"` 14 | } 15 | 16 | func replaceAttr(_ []string, a slog.Attr) slog.Attr { 17 | switch a.Value.Kind() { 18 | case slog.KindAny: 19 | switch v := a.Value.Any().(type) { 20 | case error: 21 | a.Value = fmtErr(v) 22 | } 23 | } 24 | return a 25 | } 26 | 27 | // marshalStack extracts stack frames from the error 28 | func marshalStack(err error) []stackFrame { 29 | trace := xerrors.StackTrace(err) 30 | 31 | if len(trace) == 0 { 32 | return nil 33 | } 34 | 35 | frames := trace.Frames() 36 | 37 | s := make([]stackFrame, len(frames)) 38 | 39 | for i, v := range frames { 40 | f := stackFrame{ 41 | Source: filepath.Join( 42 | filepath.Base(filepath.Dir(v.File)), 43 | filepath.Base(v.File), 44 | ), 45 | Func: filepath.Base(v.Function), 46 | Line: v.Line, 47 | } 48 | 49 | s[i] = f 50 | } 51 | 52 | return s 53 | } 54 | 55 | // fmtErr returns a slog.Value with keys `msg` and `trace`. If the error 56 | // does not implement interface { StackTrace() errors.StackTrace }, the `trace` 57 | // key is omitted. 58 | func fmtErr(err error) slog.Value { 59 | var groupValues []slog.Attr 60 | 61 | groupValues = append(groupValues, slog.String("msg", err.Error())) 62 | 63 | frames := marshalStack(err) 64 | 65 | if frames != nil { 66 | groupValues = append(groupValues, 67 | slog.Any("trace", frames), 68 | ) 69 | } 70 | 71 | return slog.GroupValue(groupValues...) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/tpm2/encoding.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "encoding/hex" 7 | ) 8 | 9 | // Encodes bytes to hexidecimal form 10 | func Encode(bytes []byte) string { 11 | return hex.EncodeToString(bytes) 12 | } 13 | 14 | // Decodes hexidecimal form to byte array 15 | func Decode(s string) ([]byte, error) { 16 | return hex.DecodeString(s) 17 | } 18 | 19 | // Encodes a quote to binary using the encoding/gob package 20 | func EncodeQuote(quote Quote) ([]byte, error) { 21 | buf := new(bytes.Buffer) 22 | encoder := gob.NewEncoder(buf) 23 | if err := encoder.Encode(quote); err != nil { 24 | return nil, err 25 | } 26 | return buf.Bytes(), nil 27 | } 28 | 29 | // Decodes a quote from binary using the encoding/gob package 30 | func DecodeQuote(quote []byte) (Quote, error) { 31 | var q Quote 32 | buf := bytes.NewBuffer(quote) 33 | decoder := gob.NewDecoder(buf) 34 | if err := decoder.Decode(&q); err != nil { 35 | return Quote{}, err 36 | } 37 | return q, nil 38 | } 39 | 40 | // Encodes a PCR bank slice to binary using the encoding/gob package 41 | func EncodePCRs(pcrBanks []PCRBank) ([]byte, error) { 42 | buf := new(bytes.Buffer) 43 | encoder := gob.NewEncoder(buf) 44 | if err := encoder.Encode(pcrBanks); err != nil { 45 | return nil, err 46 | } 47 | return buf.Bytes(), nil 48 | } 49 | 50 | // Decodes a PCR bank slice from binary using the encoding/gob package 51 | func DecodePCRs(pcrBanks []byte) ([]PCRBank, error) { 52 | banks := make([]PCRBank, 0) 53 | buf := bytes.NewBuffer(pcrBanks) 54 | decoder := gob.NewDecoder(buf) 55 | if err := decoder.Decode(&banks); err != nil { 56 | return nil, err 57 | } 58 | return banks, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/factory_test.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/serializer" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestInterfaces(t *testing.T) { 13 | 14 | logger := logging.DefaultLogger() 15 | 16 | config := &datastore.Config{ 17 | Backend: datastore.BackendAferoMemory.String(), 18 | ConsistencyLevel: "local", 19 | ReadBufferSize: 50, 20 | RootDir: "./", 21 | Serializer: serializer.SERIALIZER_JSON.String(), 22 | } 23 | 24 | accountID := uint64(1) 25 | 26 | factory, err := NewFactory(logger, config) 27 | assert.Nil(t, err) 28 | 29 | acmeAccountDAO, err := factory.ACMEAccountDAO() 30 | assert.Nil(t, err) 31 | assert.IsType(t, &ACMEAccountDAO{}, acmeAccountDAO) 32 | 33 | acmeOrderDAO, err := factory.ACMEOrderDAO(accountID) 34 | assert.Nil(t, err) 35 | assert.IsType(t, &ACMEOrderDAO{}, acmeOrderDAO) 36 | 37 | acmeChallengeDAO, err := factory.ACMEChallengeDAO(accountID) 38 | assert.Nil(t, err) 39 | assert.IsType(t, &ACMEChallengeDAO{}, acmeChallengeDAO) 40 | 41 | acmeAuthorizationDAO, err := factory.ACMEAuthorizationDAO(accountID) 42 | assert.Nil(t, err) 43 | assert.IsType(t, &ACMEAuthorizationDAO{}, acmeAuthorizationDAO) 44 | 45 | acmeCertificateDAO, err := factory.ACMECertificateDAO() 46 | assert.Nil(t, err) 47 | assert.IsType(t, &ACMECertificateDAO{}, acmeCertificateDAO) 48 | 49 | acmeNonceDAO, err := factory.ACMENonceDAO() 50 | assert.Nil(t, err) 51 | assert.IsType(t, &ACMENonceDAO{}, acmeNonceDAO) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/cmd/platform/policy.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "github.com/google/go-tpm/tpm2" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var PolicyCmd = &cobra.Command{ 9 | Use: "policy [action]", 10 | Short: "Platform PCR policy operations", 11 | Long: `Perform platform PCR policy operations.`, 12 | Run: func(cmd *cobra.Command, args []string) { 13 | 14 | App, err = App.Init(InitParams) 15 | if err != nil { 16 | cmd.PrintErrln(err) 17 | return 18 | } 19 | 20 | var digestHash []byte 21 | var err error 22 | 23 | if len(args) == 0 { 24 | digestHash, err = App.TPM.PlatformPolicyDigestHash() 25 | if err != nil { 26 | cmd.PrintErrln(err) 27 | return 28 | } 29 | cmd.Printf("Hash: %x\n", digestHash) 30 | return 31 | } 32 | 33 | switch args[0] { 34 | case "create": 35 | if err := App.TPM.CreatePlatformPolicy(); err != nil { 36 | cmd.PrintErrln(err) 37 | return 38 | } 39 | digestHash, err = App.TPM.PlatformPolicyDigestHash() 40 | if err != nil { 41 | cmd.PrintErrln(err) 42 | return 43 | } 44 | 45 | case "session": 46 | session, closer, err := App.TPM.PlatformPolicySession() 47 | if err != nil { 48 | cmd.PrintErrln(err) 49 | return 50 | } 51 | defer closer() 52 | 53 | digestHash, err = App.TPM.PlatformPolicyDigestHash() 54 | if err != nil { 55 | cmd.PrintErrln(err) 56 | return 57 | } 58 | 59 | pgd, err := tpm2.PolicyGetDigest{ 60 | PolicySession: session.Handle(), 61 | }.Execute(App.TPM.Transport()) 62 | 63 | cmd.Printf("PolicyDigest.Buffer: %x\n", pgd.PolicyDigest.Buffer) 64 | cmd.Printf("Hash: %x\n", digestHash) 65 | } 66 | 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /pkg/store/blob/file_test.go: -------------------------------------------------------------------------------- 1 | package blob 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 9 | "github.com/spf13/afero" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBlobStore(t *testing.T) { 14 | 15 | data := []byte("test") 16 | 17 | store := createStore() 18 | 19 | blobKey := []byte("/test/path.txt") 20 | 21 | // Save the blob 22 | err := store.Save(blobKey, data) 23 | assert.Nil(t, err) 24 | 25 | // Get the persisted blob 26 | persisted, err := store.Get(blobKey) 27 | assert.Nil(t, err) 28 | assert.Equal(t, data, persisted) 29 | 30 | // Ensure it exists 31 | path := store.Exists(blobKey) 32 | assert.True(t, path) 33 | 34 | // Delete the org 35 | err = store.Delete(blobKey) 36 | assert.Nil(t, err) 37 | 38 | // Ensure it's deleted 39 | _, err = store.Get(blobKey) 40 | assert.NotNil(t, err) 41 | assert.True(t, errors.Is(err, ErrBlobNotFound)) 42 | } 43 | 44 | func TestCount(t *testing.T) { 45 | 46 | data := []byte("test") 47 | 48 | store := createStore() 49 | 50 | partition := "/test" 51 | 52 | count := 1000 53 | for i := 0; i < count; i++ { 54 | filename := fmt.Sprintf("%s/file-%d.txt", partition, i) 55 | err := store.Save([]byte(filename), data) 56 | assert.Nil(t, err) 57 | } 58 | 59 | _count, err := store.Count(&partition) 60 | assert.Nil(t, err) 61 | assert.True(t, _count == count) 62 | } 63 | 64 | func createStore() BlobStorer { 65 | 66 | logger := logging.DefaultLogger() 67 | 68 | caCN := "example.com" 69 | 70 | fs := afero.NewMemMapFs() 71 | store, err := NewFSBlobStore(logger, fs, caCN, nil) 72 | if err != nil { 73 | logger.FatalError(err) 74 | } 75 | 76 | return store 77 | } 78 | -------------------------------------------------------------------------------- /pkg/platform/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 9 | ) 10 | 11 | var ( 12 | ErrUnsupportedAuthType = errors.New("unsupported auth type") 13 | ErrUserNotFound = errors.New("user not found") 14 | ) 15 | 16 | type UserServicer interface { 17 | Save(user *entities.User) error 18 | Delete(session Session, userID uint64) error 19 | Get(userID uint64) (*entities.User, error) 20 | } 21 | 22 | type User struct { 23 | logger *logging.Logger 24 | userDAO datastore.UserDAO 25 | orgDAO datastore.OrganizationDAO 26 | roleDAO datastore.RoleDAO 27 | UserServicer 28 | } 29 | 30 | func NewUserService( 31 | logger *logging.Logger, 32 | userDAO datastore.UserDAO, 33 | orgDAO datastore.OrganizationDAO, 34 | roleDAO datastore.RoleDAO, 35 | authServices map[int]AuthServicer) UserServicer { 36 | 37 | return &User{ 38 | logger: logger, 39 | userDAO: userDAO, 40 | orgDAO: orgDAO, 41 | roleDAO: roleDAO} 42 | } 43 | 44 | // Create a new user 45 | func (service *User) Save(user *entities.User) error { 46 | return service.userDAO.Save(user) 47 | } 48 | 49 | // Delete an existing user 50 | func (service *User) Delete(session Session, userID uint64) error { 51 | return service.userDAO.Delete(&entities.User{ID: userID}) 52 | } 53 | 54 | // Retrieves a user with the given id or ErrRecordNotFound 55 | func (service *User) Get(userID uint64) (*entities.User, error) { 56 | userEntity, err := service.userDAO.Get(userID, datastore.ConsistencyLevelLocal) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return userEntity, nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/tpm2/secretkey.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "github.com/google/go-tpm/tpm2" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 6 | ) 7 | 8 | // Creates a new RSA child key using the provided key attributes 9 | func (tpm *TPM2) CreateSecretKey( 10 | keyAttrs *keystore.KeyAttributes, 11 | backend keystore.KeyBackend) error { 12 | 13 | if keyAttrs.Parent == nil { 14 | return keystore.ErrInvalidKeyAttributes 15 | } 16 | 17 | // Get the persisted SRK 18 | srkHandle := tpm2.TPMHandle(keyAttrs.Parent.TPMAttributes.Handle) 19 | srkName, _, err := tpm.ReadHandle(srkHandle) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | // Create new AES key under the SRK, optionally protected by the 25 | // platform auth policy that requires the platform PCR value with 26 | // the Golden Integrity Measurements to release. 27 | aesTemplate := AES256CFBTemplate 28 | 29 | // Attach platform PCR policy digest if configured 30 | if keyAttrs.PlatformPolicy { 31 | aesTemplate.AuthPolicy = tpm.PlatformPolicyDigest() 32 | } 33 | 34 | // Create the parent key authorization session 35 | session, closer, err := tpm.CreateSession(keyAttrs) 36 | if err != nil { 37 | return err 38 | } 39 | defer closer() 40 | 41 | response, err := tpm2.Create{ 42 | ParentHandle: tpm2.AuthHandle{ 43 | Handle: srkHandle, 44 | Name: srkName, 45 | Auth: session, 46 | }, 47 | InPublic: tpm2.New2B(aesTemplate), 48 | }.Execute(tpm.transport) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | // Save the public and private areas to blob storage 54 | if err := tpm.SaveKeyPair( 55 | keyAttrs, 56 | response.OutPrivate, 57 | response.OutPublic, 58 | backend, 59 | false); err != nil { 60 | 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/ca/trust_store_debian.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 8 | "github.com/spf13/afero" 9 | ) 10 | 11 | type DebianTrustStore struct { 12 | logger *logging.Logger 13 | certDir string 14 | fs afero.Fs 15 | storeDir string 16 | OSTrustStore 17 | } 18 | 19 | func NewDebianTrustStore(logger *logging.Logger, fs afero.Fs, certDir string) OSTrustStore { 20 | return &DebianTrustStore{ 21 | logger: logger, 22 | certDir: certDir, 23 | fs: fs, 24 | storeDir: "/usr/local/share/ca-certificates"} 25 | } 26 | 27 | func (store *DebianTrustStore) Install(cn string) error { 28 | store.logger.Debugf( 29 | "installing %s.crt to operating system trusted certificate store: %s", 30 | cn, store.storeDir) 31 | certFile := fmt.Sprintf("%s/%s.crt", store.certDir, cn) 32 | storeFile := fmt.Sprintf("%s/%s.crt", store.storeDir, cn) 33 | data, err := afero.ReadFile(store.fs, certFile) 34 | if err != nil { 35 | return err 36 | } 37 | if err = afero.WriteFile(store.fs, storeFile, data, 0644); err != nil { 38 | return err 39 | } 40 | return store.UpdateCerts() 41 | } 42 | 43 | func (store *DebianTrustStore) Uninstall(cn string) error { 44 | store.logger.Debugf("uninstalling %s.crt from operating system trusted certificate store: %s", 45 | cn, store.storeDir) 46 | storeFile := fmt.Sprintf("%s/%s.crt", store.storeDir, cn) 47 | if err := store.fs.Remove(storeFile); err != nil { 48 | return err 49 | } 50 | return store.UpdateCerts() 51 | } 52 | 53 | func (store *DebianTrustStore) UpdateCerts() error { 54 | cmd := exec.Command("update-ca-certificates") 55 | stdout, err := cmd.Output() 56 | if err != nil { 57 | return err 58 | } 59 | store.logger.Infof("update-ca-certificates: %s", string(stdout)) 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/store/keystore/dilithium2/dilithium2.go: -------------------------------------------------------------------------------- 1 | //go:build quantum_safe 2 | 3 | package dilithium2 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/open-quantum-safe/liboqs-go/oqs" 9 | ) 10 | 11 | var ( 12 | QUANTUM_ALGORITHM_DILITHIUM2 = "Dilithium2" 13 | ) 14 | 15 | type Dilithium2 struct { 16 | signer *oqs.Signature 17 | } 18 | 19 | // Generate a new Dilithium2 key pair instance 20 | func New() (*Dilithium2, error) { 21 | signer := oqs.Signature{} 22 | if err := signer.Init("Dilithium2", nil); err != nil { 23 | return nil, err 24 | } 25 | return &Dilithium2{signer: &signer}, nil 26 | } 27 | 28 | func (d *Dilithium2) Create(secretKey []byte) error { 29 | d.Clean() 30 | d.signer = &oqs.Signature{} 31 | if err := d.signer.Init("Dilithium2", secretKey); err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | // Clean the Dilithium2 library instance 38 | func (d *Dilithium2) Clean() { 39 | if d.signer != nil { 40 | d.signer.Clean() 41 | d.signer = nil 42 | } 43 | } 44 | 45 | // Generate a new Dilithium2 key pair 46 | func (d *Dilithium2) GenerateKeyPair() ([]byte, error) { 47 | pubKey, err := d.signer.GenerateKeyPair() 48 | if err != nil { 49 | return nil, err 50 | } 51 | return pubKey, nil 52 | } 53 | 54 | // Exports the private key 55 | func (d *Dilithium2) ExportSecretKey() []byte { 56 | return d.signer.ExportSecretKey() 57 | } 58 | 59 | // Sign a message with the secret key 60 | func (d *Dilithium2) Sign(data []byte) ([]byte, error) { 61 | return d.signer.Sign(data) 62 | } 63 | 64 | // Verify a message with the public key 65 | func (d *Dilithium2) Verify(data, signature, publicKey []byte) error { 66 | valid, err := d.signer.Verify(data, signature, publicKey) 67 | if err != nil { 68 | return err 69 | } 70 | if !valid { 71 | return errors.New("signature verification failed") 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/cmd/status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/certstore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | 13 | rootCmd.AddCommand(statusCmd) 14 | } 15 | 16 | var statusCmd = &cobra.Command{ 17 | Use: "status", 18 | Short: "Displays the Trusted Platform status", 19 | Long: `Displays information about the current status of the Trusted Platform 20 | installation, including TPM keys & certificates, Certificate Authority, and 21 | running services.`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | 24 | App, err = App.Init(InitParams) 25 | if err != nil { 26 | cmd.PrintErrln(err) 27 | return 28 | } 29 | 30 | info, err := App.TPM.Info() 31 | if err != nil { 32 | cmd.PrintErrln(err) 33 | return 34 | } 35 | 36 | fmt.Println(info) 37 | 38 | fmt.Println("Endorsement Key (EK)") 39 | ekAttrs, _ := App.TPM.EKAttributes() 40 | cmd.Println(ekAttrs.String()) 41 | cmd.Println(keystore.PublicKeyToString(ekAttrs)) 42 | 43 | fmt.Println("EK Certificate (EK Credential Profile)") 44 | ekCert, _ := App.TPM.EKCertificate() 45 | cmd.Println(certstore.ToString(ekCert)) 46 | 47 | fmt.Println("Shared Storage Root Key (SSRK)") 48 | ssrkAttrs, _ := App.TPM.SSRKAttributes() 49 | cmd.Println(ssrkAttrs.String()) 50 | cmd.Println(keystore.PublicKeyToString(ssrkAttrs)) 51 | 52 | fmt.Println("Initial Attestation Key (IAK)") 53 | iakAttrs, _ := App.TPM.IAKAttributes() 54 | cmd.Println(iakAttrs.String()) 55 | cmd.Println(keystore.PublicKeyToString(iakAttrs)) 56 | 57 | fmt.Println("Initial Device ID (IDevID)") 58 | idevidAttrs, _ := App.TPM.IDevIDAttributes() 59 | cmd.Println(idevidAttrs.String()) 60 | cmd.Println(keystore.PublicKeyToString(idevidAttrs)) 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /pkg/platform/service/registration.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 9 | ) 10 | 11 | var ( 12 | ErrRegistrationNotFound = errors.New("registration not found") 13 | ) 14 | 15 | type RegistrationServicer interface { 16 | Create(registration *entities.Registration) error 17 | Delete(session Session, registrationID uint64) error 18 | Get(session Session, registrationID uint64) (*entities.Registration, error) 19 | } 20 | 21 | type Registration struct { 22 | logger *logging.Logger 23 | registrationDAO datastore.RegistrationDAO 24 | RegistrationServicer 25 | } 26 | 27 | func NewRegistrationService( 28 | logger *logging.Logger, 29 | registrationDAO datastore.RegistrationDAO) RegistrationServicer { 30 | 31 | return &Registration{ 32 | logger: logger, 33 | registrationDAO: registrationDAO} 34 | } 35 | 36 | // Deletes an existing registration account 37 | func (service *Registration) Delete(session Session, registrationID uint64) error { 38 | return service.registrationDAO.Delete( 39 | &entities.Registration{ 40 | ID: registrationID, 41 | }) 42 | } 43 | 44 | // Looks up the registration account by registration ID 45 | func (service *Registration) Get(session Session, registrationID uint64) (*entities.Registration, error) { 46 | entity, err := service.registrationDAO.Get(registrationID, datastore.ConsistencyLevelLocal) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return entity, nil 51 | } 52 | 53 | // CreateRegistration creates a new registration account 54 | func (service *Registration) Create(registration *entities.Registration) error { 55 | return service.registrationDAO.Save(registration) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/platform/service/types.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 7 | ) 8 | 9 | var ( 10 | ErrCreateService = errors.New("service: failed to create service") 11 | ErrPermissionDenied = errors.New("service: permission denied") 12 | ErrDeleteAdminAccount = errors.New("service: admin account can't be deleted") 13 | ErrChangeAdminRole = errors.New("service: admin role can't be changed") 14 | ErrResetPasswordUnsupported = errors.New("service: reset password feature unsupported by auth store") 15 | ) 16 | 17 | type UserCredential struct { 18 | OrgID uint64 `json:"org"` 19 | Email string `json:"email"` 20 | Password string `json:"password"` 21 | AuthType int `json:"authType"` 22 | } 23 | 24 | // Claim structs are condensed models concerned only 25 | // with users, roles, permissions, and licensing between 26 | // the client and server. They get exchanged with every 27 | // request and are used to generate a "Session" for working 28 | // with business logic services in the "service" package. 29 | // type ServiceClaim struct { 30 | // ID uint64 `json:"id"` 31 | // Name string `json:"name"` 32 | // Roles []string `json:"roles"` 33 | // } 34 | 35 | // type OrganizationClaim struct { 36 | // ID uint64 `json:"id"` 37 | // Name string `json:"name"` 38 | // Services []ServiceClaim `json:"farms"` 39 | // Roles []string `json:"roles"` 40 | // } 41 | 42 | type AuthServicer interface { 43 | Activate(registrationID uint64) (*entities.User, error) 44 | Login(userCredentials *UserCredential) (*entities.User, []*entities.Organization, []*entities.Service, error) 45 | Register(userCredentials *UserCredential, baseURI string) (*entities.User, error) 46 | ResetPassword(userCredentials *UserCredential) error 47 | } 48 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/ca_bundle.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 10 | ) 11 | 12 | func (s *RestService) CABundleHandler(w http.ResponseWriter, r *http.Request) { 13 | 14 | s.logger.Debug("CABundleHandler", "method", r.Method, "url", r.URL) 15 | for name, values := range r.Header { 16 | for _, value := range values { 17 | s.logger.Debugf("%s: %s\n", name, value) 18 | } 19 | } 20 | 21 | var err error 22 | 23 | vars := mux.Vars(r) 24 | varStoreType := vars["storeType"] 25 | varKeyAlgo := vars["keyAlgo"] 26 | 27 | var bundle []byte 28 | if varStoreType == "" && varKeyAlgo == "" { 29 | 30 | // Serve the CA bundle that was used to sign the web server TLS certificate 31 | bundle, err = s.ca.CABundle(&s.tlsStoreType, &s.tlsKeyAlgorithm) 32 | if err != nil { 33 | writeError(w, acme.ServerInternal("Failed to get CA bundle")) 34 | return 35 | } 36 | 37 | } else { 38 | 39 | // Serve the CA bundle that was requested by the client 40 | storeType, err := keystore.ParseStoreType(strings.ToLower(varStoreType)) 41 | if err != nil { 42 | writeError(w, acme.MalformedError("Invalid store type", nil)) 43 | return 44 | } 45 | 46 | keyAlgo, err := keystore.ParseKeyAlgorithm(varKeyAlgo) 47 | if err != nil { 48 | writeError(w, acme.MalformedError("Invalid key algorithm", nil)) 49 | return 50 | } 51 | 52 | bundle, err = s.ca.CABundle(&storeType, &keyAlgo) 53 | if err != nil { 54 | writeError(w, acme.ServerInternal("Failed to get CA bundle")) 55 | return 56 | } 57 | } 58 | 59 | w.Header().Set("Cache-Control", "no-store") 60 | w.Header().Set("Content-Type", "application/pem-certificate-chain") 61 | w.WriteHeader(http.StatusOK) 62 | w.Write(bundle) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/store/certstore/backend_test.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | "fmt" 7 | "os" 8 | "testing" 9 | 10 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 11 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/blob" 12 | "github.com/spf13/afero" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func FileExists(path string) bool { 17 | _, err := os.Stat(path) 18 | if err == nil { 19 | return true 20 | } 21 | if os.IsNotExist(err) { 22 | return false 23 | } 24 | return false 25 | } 26 | 27 | func TestSaveAndGetCA(t *testing.T) { 28 | 29 | backend, _ := defaultStore() 30 | 31 | cert, err := DecodePEM(TEST_CERT_PEM) 32 | assert.Nil(t, err) 33 | 34 | id, err := ParseCertificateID(cert, nil) 35 | assert.Nil(t, err) 36 | 37 | expectedPath := fmt.Sprintf("%s/blobs/%s", TEST_DATA_DIR, id) 38 | 39 | err = backend.ImportCertificate(id, cert) 40 | assert.Nil(t, err) 41 | assert.True(t, FileExists(expectedPath)) 42 | 43 | err = backend.DeleteCertificate(id) 44 | assert.Nil(t, err) 45 | assert.False(t, FileExists(expectedPath)) 46 | 47 | _, err = backend.Get(id) 48 | assert.Equal(t, ErrCertNotFound, err) 49 | } 50 | 51 | func defaultStore() (CertificateBackend, string) { 52 | 53 | logger := logging.DefaultLogger() 54 | 55 | // Create a temp directory for each instantiation 56 | // so parallel tests don't corrupt each other. 57 | buf := make([]byte, 8) 58 | _, err := rand.Reader.Read(buf) 59 | if err != nil { 60 | panic(err) 61 | } 62 | tmpDir := hex.EncodeToString(buf) 63 | 64 | caCN := "example.com" 65 | temp := fmt.Sprintf("%s/%s/%s", TEST_DATA_DIR, tmpDir, caCN) 66 | 67 | fs := afero.NewMemMapFs() 68 | blobStore, err := blob.NewFSBlobStore(logger, fs, TEST_DATA_DIR, nil) 69 | if err != nil { 70 | logger.FatalError(err) 71 | } 72 | 73 | return NewBlobStoreBackend(blobStore), temp 74 | } 75 | -------------------------------------------------------------------------------- /pkg/store/datastore/kvstore/webauthn.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 6 | ) 7 | 8 | const ( 9 | webauthn_partition = "webauthn" 10 | ) 11 | 12 | type WebAuthnDAO struct { 13 | *AferoDAO[*entities.Blob] 14 | } 15 | 16 | func NewWebAuthnDAO(params *datastore.Params[*entities.Blob]) (datastore.WebAuthnDAO, error) { 17 | if params.Partition == "" { 18 | params.Partition = webauthn_partition 19 | } 20 | aferoDAO, err := NewAferoDAO[*entities.Blob](params) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &WebAuthnDAO{ 25 | AferoDAO: aferoDAO, 26 | }, nil 27 | } 28 | 29 | // func (webauthnDAO *WebAuthnDAO) Save(entity *entities.Blob) error { 30 | // return webauthnDAO.AferoDAO.Save(entity) 31 | // } 32 | 33 | // func (webauthnDAO *WebAuthnDAO) Get(id uint64, CONSISTENCY_LEVEL int) (*entities.Blob, error) { 34 | // return webauthnDAO.AferoDAO.Get(id, CONSISTENCY_LEVEL) 35 | // } 36 | 37 | // func (webauthnDAO *WebAuthnDAO) Delete(entity *entities.Blob) error { 38 | // return webauthnDAO.AferoDAO.Delete(entity) 39 | // } 40 | 41 | // func (webauthnDAO *WebAuthnDAO) Count(CONSISTENCY_LEVEL int) (int, error) { 42 | // return webauthnDAO.AferoDAO.Count(CONSISTENCY_LEVEL) 43 | // } 44 | 45 | // func (webauthnDAO *WebAuthnDAO) Page( 46 | // pageQuery datastore.PageQuery, 47 | // CONSISTENCY_LEVEL int) (datastore.PageResult[*entities.Blob], error) { 48 | 49 | // return webauthnDAO.AferoDAO.Page(pageQuery, CONSISTENCY_LEVEL) 50 | // } 51 | 52 | // func (webauthnDAO *WebAuthnDAO) ForEachPage( 53 | // pageQuery datastore.PageQuery, 54 | // pagerProcFunc datastore.PagerProcFunc[*entities.Blob], 55 | // CONSISTENCY_LEVEL int) error { 56 | 57 | // return webauthnDAO.AferoDAO.ForEachPage(pageQuery, pagerProcFunc, CONSISTENCY_LEVEL) 58 | // } 59 | -------------------------------------------------------------------------------- /pkg/webservice/v1/test/handler_key_change_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/gorilla/mux" 11 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/server/handlers" 12 | "github.com/jeremyhahn/go-trusted-platform/pkg/webservice/v1/router" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestKeyChangeHandler(t *testing.T) { 17 | 18 | // Set up a mock ACME service 19 | // mockService := &MockACMEService{} 20 | acmeRouter := router.NewACMERouter(RestServiceRegistry.ACMERestService()) 21 | 22 | // Create a new router and register the key-change endpoint 23 | r := mux.NewRouter() 24 | acmeRouter.RegisterRoutes(r) 25 | 26 | acmeURL := "https://localhost:8443/api/v1/acme/account/16268186706719120207" 27 | 28 | // Define the key-change request body 29 | keyChangeRequest := handlers.KeyChangeRequest{ 30 | AccountURL: acmeURL, 31 | // OldKey: "mock-old-key", 32 | // NewKey: "mock-new-key", 33 | } 34 | 35 | // Convert the request body to JSON 36 | requestBodyBytes, err := json.Marshal(keyChangeRequest) 37 | assert.NoError(t, err) 38 | 39 | // Create a new HTTP request for the key-change endpoint 40 | req, err := http.NewRequest("POST", "/api/v1/acme/key-change", bytes.NewBuffer(requestBodyBytes)) 41 | assert.NoError(t, err) 42 | req.Header.Set("Content-Type", "application/json") 43 | 44 | // Create a response recorder to capture the HTTP response 45 | rr := httptest.NewRecorder() 46 | 47 | // Call the router with the request 48 | r.ServeHTTP(rr, req) 49 | 50 | // Check the status code is 200 OK 51 | assert.Equal(t, http.StatusOK, rr.Code) 52 | 53 | // Check that the response contains the expected success message 54 | expectedResponse := `{"message": "Key updated successfully"}` 55 | assert.JSONEq(t, expectedResponse, rr.Body.String()) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/dns/tld.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 11 | ) 12 | 13 | var ( 14 | ianaTLDs map[string]bool 15 | ) 16 | 17 | // Loads and processes the provided tldData as a plain text file 18 | // with each TLD on it's own line. If the tldData is not provided, 19 | // the TLD list from data.iana.org will be used as a default. 20 | func LoadTLDs(logger *logging.Logger, tldData []byte) error { 21 | 22 | if tldData == nil { 23 | 24 | url := "https://data.iana.org/TLD/tlds-alpha-by-domain.txt" 25 | 26 | resp, err := http.Get(url) 27 | if err != nil { 28 | return fmt.Errorf("failed to fetch TLD list: %w", err) 29 | } 30 | defer resp.Body.Close() 31 | 32 | tldData, err = io.ReadAll(resp.Body) 33 | if err != nil { 34 | return fmt.Errorf("failed to read TLD list: %w", err) 35 | } 36 | 37 | } 38 | 39 | tldSet := make(map[string]bool) 40 | scanner := bufio.NewScanner(strings.NewReader(string(tldData))) 41 | 42 | for scanner.Scan() { 43 | line := strings.TrimSpace(scanner.Text()) 44 | 45 | // Skip comments and empty lines 46 | if strings.HasPrefix(line, "#") || line == "" { 47 | continue 48 | } 49 | // Store TLDs in lowercase 50 | tldSet[strings.ToLower(line)] = true 51 | } 52 | 53 | if err := scanner.Err(); err != nil { 54 | return fmt.Errorf("error reading TLD list: %w", err) 55 | } 56 | 57 | // Create a memory packed map without any extra space from 58 | // dynamic resizing to cache for the life of the process 59 | ianaTLDs = make(map[string]bool, len(tldSet)) 60 | for tld, _ := range tldSet { 61 | ianaTLDs[tld] = true 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // IsTLD checks if a given TLD exists in the map (case-insensitively). 68 | func IsTLD(tld string) bool { 69 | _, exists := ianaTLDs[strings.ToLower(tld)] 70 | return exists 71 | } 72 | -------------------------------------------------------------------------------- /pkg/webservice/mtls_client.go: -------------------------------------------------------------------------------- 1 | package webservice 2 | 3 | import ( 4 | "crypto/tls" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/ca" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 10 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 11 | ) 12 | 13 | type MutualTLSClient struct { 14 | logger *logging.Logger 15 | http http.Client 16 | ca ca.CertificateAuthority 17 | } 18 | 19 | func NewMutalTLSClient( 20 | logger *logging.Logger, 21 | ca ca.CertificateAuthority, 22 | attrs *keystore.KeyAttributes) MutualTLSClient { 23 | 24 | client := MutualTLSClient{logger: logger, ca: ca} 25 | client.http = http.Client{ 26 | Transport: &http.Transport{ 27 | TLSClientConfig: &tls.Config{ 28 | GetClientCertificate: func( 29 | info *tls.CertificateRequestInfo) (*tls.Certificate, error) { 30 | return client.certificate(attrs) 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | return client 37 | } 38 | 39 | // Performs an HTTP GET request 40 | func (client MutualTLSClient) Get(url string) ([]byte, error) { 41 | 42 | response, err := client.http.Get(url) 43 | if err != nil { 44 | client.logger.Error(err) 45 | return nil, err 46 | } 47 | defer response.Body.Close() 48 | 49 | bytes, err := io.ReadAll(response.Body) 50 | if err != nil { 51 | client.logger.Error(err) 52 | return nil, err 53 | } 54 | 55 | return bytes, nil 56 | } 57 | 58 | // Retrieves the mTLS client certificate from the Certificate Authority 59 | func (client MutualTLSClient) certificate(attrs *keystore.KeyAttributes) (*tls.Certificate, error) { 60 | 61 | client.logger.Infof("requesting client certificate: %s", attrs.CN) 62 | 63 | cert, err := client.ca.Certificate(attrs) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | certificate := tls.Certificate{ 69 | Certificate: [][]byte{cert.Raw}, 70 | PrivateKey: &Signer{cert: cert}, 71 | } 72 | return &certificate, nil 73 | } 74 | -------------------------------------------------------------------------------- /pkg/store/certstore/encoding.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509" 6 | "encoding/pem" 7 | 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 9 | ) 10 | 11 | // Encodes a raw DER byte array as a PEM byte array 12 | func EncodePEM(derCert []byte) ([]byte, error) { 13 | caPEM := new(bytes.Buffer) 14 | err := pem.Encode(caPEM, &pem.Block{ 15 | Type: "CERTIFICATE", 16 | Bytes: derCert, 17 | }) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return caPEM.Bytes(), nil 22 | } 23 | 24 | // Decodes PEM bytes to *x509.Certificate 25 | func DecodePEM(bytes []byte) (*x509.Certificate, error) { 26 | var block *pem.Block 27 | if block, _ = pem.Decode(bytes); block == nil { 28 | return nil, keystore.ErrInvalidEncodingPEM 29 | } 30 | return x509.ParseCertificate(block.Bytes) 31 | } 32 | 33 | // Encodes a Certificate Signing Request to PEM form 34 | func EncodeCSR(csr []byte) ([]byte, error) { 35 | csrPEM := new(bytes.Buffer) 36 | csrBlock := &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr} 37 | if err := pem.Encode(csrPEM, csrBlock); err != nil { 38 | return nil, err 39 | } 40 | return csrPEM.Bytes(), nil 41 | } 42 | 43 | // Decodes CSR bytes to x509.CertificateRequest 44 | func DecodeCSR(bytes []byte) (*x509.CertificateRequest, error) { 45 | var block *pem.Block 46 | if block, _ = pem.Decode(bytes); block == nil { 47 | return nil, keystore.ErrInvalidEncodingPEM 48 | } 49 | return x509.ParseCertificateRequest(block.Bytes) 50 | } 51 | 52 | // Decodes a PEM certificate chain 53 | func DecodePEMChain(bytes []byte) ([]*x509.Certificate, error) { 54 | var certs []*x509.Certificate 55 | for block, rest := pem.Decode(bytes); block != nil; block, rest = pem.Decode(rest) { 56 | switch block.Type { 57 | case "CERTIFICATE": 58 | cert, err := x509.ParseCertificate(block.Bytes) 59 | if err != nil { 60 | panic(err) 61 | } 62 | certs = append(certs, cert) 63 | } 64 | } 65 | return certs, nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/crypto/aesgcm/aesgcm_test.go: -------------------------------------------------------------------------------- 1 | package aesgcm 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSealWithoutAdditionalData(t *testing.T) { 12 | 13 | // Use rand.Reader for entropy 14 | aesgcm := NewAESGCM(nil) 15 | 16 | // Generate a key 17 | key := make([]byte, 32) 18 | _, err := io.ReadFull(rand.Reader, key) 19 | assert.Nil(t, err) 20 | 21 | // Seal the data and get back the cipher-text and nonce 22 | secret := []byte("my-secret") 23 | ciphertext, nonce, err := aesgcm.Seal(key, secret, nil) 24 | 25 | // Open the cipher-text using the nonce returned from Seal 26 | decrypted, err := aesgcm.Open(key, ciphertext, nonce, nil) 27 | assert.Nil(t, err) 28 | assert.Equal(t, secret, decrypted) 29 | 30 | // Ensure failure with a bad nonce 31 | _, err = aesgcm.Open(key, ciphertext, []byte("foo"), nil) 32 | assert.NotNil(t, err) 33 | assert.Equal(t, ErrInvalidNonce, err) 34 | } 35 | 36 | func TestSealWithAdditionalData(t *testing.T) { 37 | 38 | // Use rand.Reader for entropy 39 | aesgcm := NewAESGCM(nil) 40 | 41 | // Generate a key 42 | key := make([]byte, 32) 43 | _, err := io.ReadFull(rand.Reader, key) 44 | assert.Nil(t, err) 45 | 46 | // Create "additional data". The Open operation will fail 47 | // if the additional data does not match the same data 48 | // that was passed to Seal. 49 | additionalData := []byte("some authorization data") 50 | 51 | // Seal the data and get back the cipher-text and nonce 52 | secret := []byte("my-secret") 53 | ciphertext, nonce, err := aesgcm.Seal(key, secret, additionalData) 54 | 55 | // Open the cipher-text using the nonce returned from Seal 56 | decrypted, err := aesgcm.Open(key, ciphertext, nonce, additionalData) 57 | assert.Nil(t, err) 58 | assert.Equal(t, secret, decrypted) 59 | 60 | // Ensure failure when additional data doesnt match 61 | _, err = aesgcm.Open(key, ciphertext, nonce, []byte("foo")) 62 | assert.NotNil(t, err) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/store/keystore/tpm2/signer_ecdsa_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "crypto/x509" 7 | "testing" 8 | 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestSignerECDSA(t *testing.T) { 14 | 15 | soPIN := []byte("so-pin") 16 | userPIN := []byte("testme") 17 | 18 | hierarchyAuth := keystore.NewClearPassword(soPIN) 19 | userSecret := keystore.NewClearPassword(userPIN) 20 | 21 | _, tpmks, tpm, err := createKeyStore(true, soPIN, userPIN, true) 22 | defer tpmks.Close() 23 | 24 | assert.NotNil(t, tpm) 25 | assert.NotNil(t, tpmks) 26 | assert.Equal(t, keystore.ErrNotInitalized, err) 27 | 28 | err = tpmks.Initialize(hierarchyAuth, userSecret) 29 | assert.Nil(t, err) 30 | 31 | caKey, _ := keystore.Templates[x509.ECDSA] 32 | caKey.KeyType = keystore.KEY_TYPE_CA 33 | caKey.PlatformPolicy = true 34 | caKey.SignatureAlgorithm = x509.ECDSAWithSHA256 35 | caKey.StoreType = keystore.STORE_TPM2 36 | 37 | opaqueKey, err := tpmks.GenerateECDSA(caKey) 38 | assert.Nil(t, err) 39 | assert.NotNil(t, opaqueKey) 40 | assert.NotNil(t, opaqueKey.Public()) 41 | 42 | // Define data and create digest 43 | data := []byte("some data") 44 | digest, err := opaqueKey.Digest(data) 45 | assert.Nil(t, err) 46 | assert.NotNil(t, digest) 47 | 48 | // Generate signer opts 49 | signerOpts := keystore.NewSignerOpts(caKey, data) 50 | assert.Nil(t, err) 51 | 52 | // Generate blob CN to store signed data 53 | blobCN := []byte("example.com/data.bin") 54 | signerOpts.BlobCN = blobCN 55 | 56 | // Sign the digest 57 | sig, err := opaqueKey.Sign(rand.Reader, digest, signerOpts) 58 | assert.Nil(t, err) 59 | assert.NotNil(t, sig) 60 | 61 | // Get the public key 62 | ecdsaPub, ok := opaqueKey.Public().(*ecdsa.PublicKey) 63 | assert.True(t, ok) 64 | assert.NotNil(t, ecdsaPub) 65 | 66 | // Verify the signature 67 | ok = ecdsa.VerifyASN1(ecdsaPub, digest, sig) 68 | assert.True(t, ok) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/ca/revoke.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | revokeDeleteKeys bool 10 | ) 11 | 12 | func init() { 13 | CertificateCmd.PersistentFlags().BoolVar(&revokeDeleteKeys, "delete-keys", true, "True to delete associated key pair") 14 | } 15 | 16 | var RevokeCmd = &cobra.Command{ 17 | Use: "revoke [cn] [store] [algorithm]", 18 | Short: "Revokes an issued certificate", 19 | Long: `Add the certificate to the CA Certificate Revocation List and delete 20 | the certificate and any keys from the stores.`, 21 | Args: cobra.MinimumNArgs(1), 22 | Run: func(cmd *cobra.Command, args []string) { 23 | 24 | App, err = App.Init(InitParams) 25 | if err != nil { 26 | cmd.PrintErrln(err) 27 | return 28 | } 29 | 30 | if App.CA == nil { 31 | soPIN, userPIN, err := App.ParsePINs(InitParams.SOPin, InitParams.Pin) 32 | if err != nil { 33 | App.Logger.Error(err) 34 | cmd.PrintErrln(err) 35 | return 36 | } 37 | if err := App.LoadCA(soPIN, userPIN); err != nil { 38 | cmd.PrintErrln(err) 39 | return 40 | } 41 | } 42 | 43 | cn := args[0] 44 | store := args[1] 45 | algorithm := args[2] 46 | 47 | storeType, err := keystore.ParseStoreType(store) 48 | if err != nil { 49 | cmd.PrintErrln(err) 50 | return 51 | } 52 | 53 | keyAlgo, err := keystore.ParseKeyAlgorithm(algorithm) 54 | if err != nil { 55 | cmd.PrintErrln(err) 56 | return 57 | } 58 | 59 | keyAttrs, err := keystore.Template(keyAlgo) 60 | if err != nil { 61 | cmd.PrintErrln(err) 62 | return 63 | } 64 | keyAttrs.CN = cn 65 | keyAttrs.KeyAlgorithm = keyAlgo 66 | keyAttrs.StoreType = storeType 67 | 68 | certificate, err := App.CA.Certificate(keyAttrs) 69 | if err != nil { 70 | cmd.PrintErrln(err) 71 | return 72 | } 73 | 74 | err = App.CA.Revoke(certificate, revokeDeleteKeys) 75 | if err != nil { 76 | cmd.PrintErrln(err) 77 | return 78 | } 79 | 80 | }, 81 | } 82 | -------------------------------------------------------------------------------- /examples/tss/attestor/pkg/proto/attestation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/jeremyhahn/go-trusted-platform/pkg-attestation/proto"; 4 | // option java_multiple_files = true; 5 | // option java_package = "com.jeremyhahn.go-trusted-platform.attestation"; 6 | // option java_outer_classname = "AttestationProto"; 7 | 8 | package proto; 9 | 10 | 11 | // Inseecure gRPC Attestor service 12 | service InsecureAttestor { 13 | rpc GetCABundle (CABundleRequest) returns (CABundleReply) {} 14 | } 15 | 16 | message CABundleRequest { 17 | bytes bundle = 1; 18 | } 19 | 20 | message CABundleReply { 21 | bytes bundle = 1; 22 | } 23 | 24 | message Null { 25 | } 26 | 27 | // Secure, mTLS gRPC Attestor service 28 | service TLSAttestor { 29 | rpc GetEKCert(Null) returns (EKCertReply) {} 30 | rpc GetAK(Null) returns (AKReply) {} 31 | rpc ActivateCredential(ActivateCredentialRequest) returns (ActivateCredentialResponse) {} 32 | rpc AcceptCertificate(AcceptCertificateResquest) returns (Null) {} 33 | rpc Quote(QuoteRequest) returns (QuoteResponse) {} 34 | rpc Close(Null) returns (Null) {} 35 | } 36 | 37 | message EKCertReply { 38 | bytes certificate = 1; 39 | } 40 | 41 | message AKReply { 42 | bytes EKPub = 1; 43 | bytes AKName = 2; 44 | bytes AKPub = 3; 45 | // This field deviates from the tpm2-software procedure. The signature 46 | // is required for RSA (PKCS1v15 or RSA-PSS). 47 | // https://tpm2-software.github.io/tpm2-tss/getting-started/2019/12/18/Remote-Attestation.html 48 | int32 SignatureAlgorithm = 4; 49 | } 50 | 51 | message ActivateCredentialRequest { 52 | bytes credentialBlob = 1; 53 | bytes encryptedSecret = 2; 54 | } 55 | 56 | message ActivateCredentialResponse { 57 | bytes secret = 1; 58 | } 59 | 60 | message AcceptCertificateResquest { 61 | bytes certificate = 1; 62 | } 63 | 64 | message QuoteRequest { 65 | bytes nonce = 1; 66 | repeated int32 pcrs = 2; 67 | } 68 | 69 | message QuoteResponse { 70 | bytes quote = 1; 71 | } 72 | -------------------------------------------------------------------------------- /pkg/acme/datastore_test.go: -------------------------------------------------------------------------------- 1 | package acme 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao/afero" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/serializer" 9 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBackends(t *testing.T) { 14 | 15 | logger := logging.DefaultLogger() 16 | 17 | backends := []string{ 18 | datastore.BackendAferoMemory.String(), 19 | datastore.BackendAferoFS.String(), 20 | } 21 | 22 | for _, backend := range backends { 23 | t.Run(backend, func(t *testing.T) { 24 | config := &datastore.Config{ 25 | Backend: backend, 26 | ConsistencyLevel: "local", 27 | ReadBufferSize: 50, 28 | RootDir: "./", 29 | Serializer: serializer.SERIALIZER_JSON.String(), 30 | } 31 | 32 | accountID := uint64(1) 33 | 34 | factory, err := NewDatastore(logger, config) 35 | assert.Nil(t, err) 36 | 37 | acmeAccountDAO, err := factory.ACMEAccountDAO() 38 | assert.Nil(t, err) 39 | assert.IsType(t, &afero.ACMEAccountDAO{}, acmeAccountDAO) 40 | 41 | acmeOrderDAO, err := factory.ACMEOrderDAO(accountID) 42 | assert.Nil(t, err) 43 | assert.IsType(t, &afero.ACMEOrderDAO{}, acmeOrderDAO) 44 | 45 | acmeChallengeDAO, err := factory.ACMEChallengeDAO(accountID) 46 | assert.Nil(t, err) 47 | assert.IsType(t, &afero.ACMEChallengeDAO{}, acmeChallengeDAO) 48 | 49 | acmeAuthorizationDAO, err := factory.ACMEAuthorizationDAO(accountID) 50 | assert.Nil(t, err) 51 | assert.IsType(t, &afero.ACMEAuthorizationDAO{}, acmeAuthorizationDAO) 52 | 53 | acmeCertificateDAO, err := factory.ACMECertificateDAO() 54 | assert.Nil(t, err) 55 | assert.IsType(t, &afero.ACMECertificateDAO{}, acmeCertificateDAO) 56 | 57 | acmeNonceDAO, err := factory.ACMENonceDAO() 58 | assert.Nil(t, err) 59 | assert.IsType(t, &afero.ACMENonceDAO{}, acmeNonceDAO) 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/tss/verifier/pkg/proto/attestation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/jeremyhahn/go-trusted-platform/examples/tss/verifier/pkg/proto"; 4 | // option java_multiple_files = true; 5 | // option java_package = "com.jeremyhahn.go-trusted-platform.attestation"; 6 | // option java_outer_classname = "AttestationProto"; 7 | 8 | package proto; 9 | 10 | 11 | // Inseecure gRPC Attestor service 12 | service InsecureAttestor { 13 | rpc GetCABundle (CABundleRequest) returns (CABundleReply) {} 14 | } 15 | 16 | message CABundleRequest { 17 | bytes bundle = 1; 18 | } 19 | 20 | message CABundleReply { 21 | bytes bundle = 1; 22 | } 23 | 24 | message Null { 25 | } 26 | 27 | // Secure, mTLS gRPC Attestor service 28 | service TLSAttestor { 29 | rpc GetEKCert(Null) returns (EKCertReply) {} 30 | rpc GetAK(Null) returns (AKReply) {} 31 | rpc ActivateCredential(ActivateCredentialRequest) returns (ActivateCredentialResponse) {} 32 | rpc AcceptCertificate(AcceptCertificateResquest) returns (Null) {} 33 | rpc Quote(QuoteRequest) returns (QuoteResponse) {} 34 | rpc Close(Null) returns (Null) {} 35 | } 36 | 37 | message EKCertReply { 38 | bytes certificate = 1; 39 | } 40 | 41 | message AKReply { 42 | bytes EKPub = 1; 43 | bytes AKName = 2; 44 | bytes AKPub = 3; 45 | // This field deviates from the tpm2-software procedure. The signature 46 | // is required for RSA (PKCS1v15 or RSA-PSS). 47 | // https://tpm2-software.github.io/tpm2-tss/getting-started/2019/12/18/Remote-Attestation.html 48 | int32 SignatureAlgorithm = 4; 49 | } 50 | 51 | message ActivateCredentialRequest { 52 | bytes credentialBlob = 1; 53 | bytes encryptedSecret = 2; 54 | } 55 | 56 | message ActivateCredentialResponse { 57 | bytes secret = 1; 58 | } 59 | 60 | message AcceptCertificateResquest { 61 | bytes certificate = 1; 62 | } 63 | 64 | message QuoteRequest { 65 | bytes nonce = 1; 66 | repeated int32 pcrs = 2; 67 | } 68 | 69 | message QuoteResponse { 70 | bytes quote = 1; 71 | } 72 | -------------------------------------------------------------------------------- /pkg/store/datastore/kvstore/registration.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 6 | ) 7 | 8 | const ( 9 | registration_partition = "registrations" 10 | ) 11 | 12 | type RegistrationDAO struct { 13 | *AferoDAO[*entities.Registration] 14 | } 15 | 16 | func NewRegistrationDAO(params *datastore.Params[*entities.Registration]) (datastore.RegistrationDAO, error) { 17 | if params.Partition == "" { 18 | params.Partition = registration_partition 19 | } 20 | aferoDAO, err := NewAferoDAO[*entities.Registration](params) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &RegistrationDAO{ 25 | AferoDAO: aferoDAO, 26 | }, nil 27 | } 28 | 29 | // func (registrationDAO *RegistrationDAO) Save(entity *entities.Registration) error { 30 | // return registrationDAO.AferoDAO.Save(entity) 31 | // } 32 | 33 | // func (registrationDAO *RegistrationDAO) Get(id uint64, CONSISTENCY_LEVEL int) (*entities.Registration, error) { 34 | // return registrationDAO.AferoDAO.Get(id, CONSISTENCY_LEVEL) 35 | // } 36 | 37 | // func (registrationDAO *RegistrationDAO) Delete(entity *entities.Registration) error { 38 | // return registrationDAO.AferoDAO.Delete(entity) 39 | // } 40 | 41 | // func (registrationDAO *RegistrationDAO) Count(CONSISTENCY_LEVEL int) (int, error) { 42 | // return registrationDAO.AferoDAO.Count(CONSISTENCY_LEVEL) 43 | // } 44 | 45 | // func (registrationDAO *RegistrationDAO) Page( 46 | // pageQuery datastore.PageQuery, 47 | // CONSISTENCY_LEVEL int) (datastore.PageResult[*entities.Registration], error) { 48 | 49 | // return registrationDAO.AferoDAO.Page(pageQuery, CONSISTENCY_LEVEL) 50 | // } 51 | 52 | // func (registrationDAO *RegistrationDAO) ForEachPage( 53 | // pageQuery datastore.PageQuery, 54 | // pagerProcFunc datastore.PagerProcFunc[*entities.Registration], 55 | // CONSISTENCY_LEVEL int) error { 56 | 57 | // return registrationDAO.AferoDAO.ForEachPage(pageQuery, pagerProcFunc, CONSISTENCY_LEVEL) 58 | // } 59 | -------------------------------------------------------------------------------- /pkg/webservice/v1/router/authentication.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/webservice/v1/middleware" 6 | ) 7 | 8 | type AuthenticationRouter struct { 9 | middleware middleware.JsonWebTokenMiddleware 10 | WebServiceRouter 11 | } 12 | 13 | // Creates a new web service authentication router 14 | func NewAuthenticationRouter( 15 | middleware middleware.JsonWebTokenMiddleware) WebServiceRouter { 16 | 17 | return &AuthenticationRouter{ 18 | middleware: middleware} 19 | } 20 | 21 | // Registers all of the authentication endpoints at the root of the webservice (/api/v1) 22 | func (authenticationRouter *AuthenticationRouter) RegisterRoutes(router *mux.Router) { 23 | authenticationRouter.login(router) 24 | authenticationRouter.refreshToken(router) 25 | } 26 | 27 | // @Summary Authenticate and obtain JWT 28 | // @Description Authenticate a user and returns a new JWT 29 | // @Tags Authentication 30 | // @Param UserCredential body service.UserCredential true "UserCredential struct" 31 | // @Accept json 32 | // @Produce json 33 | // @Success 200 {object} jwt.TokenClaims 34 | // @Failure 400 {object} jwt.TokenClaims 35 | // @Failure 403 {object} jwt.TokenClaims 36 | // @Failure 500 {object} jwt.TokenClaims 37 | // @Router /login [post] 38 | func (authenticationRouter *AuthenticationRouter) login(router *mux.Router) { 39 | router.HandleFunc("/login", authenticationRouter.middleware.GenerateToken) 40 | } 41 | 42 | // @Summary Refresh JWT 43 | // @Description Returns a new JWT token with a new, extended expiration date 44 | // @Tags Authentication 45 | // @Accept json 46 | // @Consume json 47 | // @Success 200 {object} jwt.TokenClaims 48 | // @Failure 400 {object} jwt.TokenClaims 49 | // @Failure 401 {object} jwt.TokenClaims 50 | // @Failure 500 {object} jwt.TokenClaims 51 | // @Router /login/refresh [get] 52 | // @Security JWT 53 | func (authenticationRouter *AuthenticationRouter) refreshToken(router *mux.Router) { 54 | router.HandleFunc("/login/refresh", authenticationRouter.middleware.RefreshToken) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/platform/service/session_webauthn.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 8 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 9 | ) 10 | 11 | var ( 12 | ErrWebAuthnNotFound = errors.New("service/webauthn: webauthn session data not found") 13 | ) 14 | 15 | type WebAuthnSessionServicer interface { 16 | Delete(session Session, sessionData *entities.Blob) error 17 | Get(session Session, id uint64) (*entities.Blob, error) 18 | Page(session Session, pageQuery datastore.PageQuery) (datastore.PageResult[*entities.Blob], error) 19 | Save(sessionData *entities.Blob) error 20 | } 21 | 22 | type WebAuthnSessionService struct { 23 | logger *logging.Logger 24 | webAuthnDAO datastore.WebAuthnDAO 25 | WebAuthnSessionServicer 26 | } 27 | 28 | func NewWebAuthnSessionService( 29 | logger *logging.Logger, 30 | webAuthnDAO datastore.WebAuthnDAO) WebAuthnSessionServicer { 31 | 32 | return &WebAuthnSessionService{ 33 | logger: logger, 34 | webAuthnDAO: webAuthnDAO} 35 | } 36 | 37 | func (service *WebAuthnSessionService) Delete(session Session, sessionData *entities.Blob) error { 38 | return service.webAuthnDAO.Delete(sessionData) 39 | } 40 | 41 | // Retrieves webauthn session data by it's session id 42 | func (service *WebAuthnSessionService) Get(session Session, id uint64) (*entities.Blob, error) { 43 | return service.webAuthnDAO.Get(id, session.ConsistencyLevel()) 44 | } 45 | 46 | // Returns a single page of webauthn session data blobs from the database 47 | func (service *WebAuthnSessionService) Page( 48 | session Session, pageQuery datastore.PageQuery) (datastore.PageResult[*entities.Blob], error) { 49 | 50 | return service.webAuthnDAO.Page(pageQuery, session.ConsistencyLevel()) 51 | } 52 | 53 | // Saves the provided webauthn session data to the data store 54 | func (service *WebAuthnSessionService) Save(sessionData *entities.Blob) error { 55 | return service.webAuthnDAO.Save(sessionData) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/store/datastore/kvstore/role.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/util" 7 | ) 8 | 9 | const ( 10 | role_partition = "roles" 11 | ) 12 | 13 | type RoleDAO struct { 14 | *AferoDAO[*entities.Role] 15 | } 16 | 17 | func NewRoleDAO(params *datastore.Params[*entities.Role]) (datastore.RoleDAO, error) { 18 | if params.Partition == "" { 19 | params.Partition = role_partition 20 | } 21 | aferoDAO, err := NewAferoDAO[*entities.Role](params) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &RoleDAO{ 26 | AferoDAO: aferoDAO, 27 | }, nil 28 | } 29 | 30 | // func (roleDAO *RoleDAO) Save(entity *entities.Role) error { 31 | // return roleDAO.AferoDAO.Save(entity) 32 | // } 33 | 34 | // func (roleDAO *RoleDAO) Get(id uint64, CONSISTENCY_LEVEL int) (*entities.Role, error) { 35 | // return roleDAO.AferoDAO.Get(id, CONSISTENCY_LEVEL) 36 | // } 37 | 38 | // func (roleDAO *RoleDAO) Delete(entity *entities.Role) error { 39 | // return roleDAO.AferoDAO.Delete(entity) 40 | // } 41 | 42 | // func (roleDAO *RoleDAO) Count(CONSISTENCY_LEVEL int) (int, error) { 43 | // return roleDAO.AferoDAO.Count(CONSISTENCY_LEVEL) 44 | // } 45 | 46 | // func (roleDAO *RoleDAO) Page( 47 | // pageQuery datastore.PageQuery, 48 | // CONSISTENCY_LEVEL int) (datastore.PageResult[*entities.Role], error) { 49 | 50 | // return roleDAO.AferoDAO.Page(pageQuery, CONSISTENCY_LEVEL) 51 | // } 52 | 53 | // func (roleDAO *RoleDAO) ForEachPage( 54 | // pageQuery datastore.PageQuery, 55 | // pagerProcFunc datastore.PagerProcFunc[*entities.Role], 56 | // CONSISTENCY_LEVEL int) error { 57 | 58 | // return roleDAO.AferoDAO.ForEachPage(pageQuery, pagerProcFunc, CONSISTENCY_LEVEL) 59 | // } 60 | 61 | func (roleDAO *RoleDAO) GetByName(name string, CONSISTENCY_LEVEL datastore.ConsistencyLevel) (*entities.Role, error) { 62 | id := util.NewID([]byte(name)) 63 | return roleDAO.Get(id, CONSISTENCY_LEVEL) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/acme/dao/afero/account.go: -------------------------------------------------------------------------------- 1 | package afero 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/dao" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme/entities" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore" 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/datastore/kvstore" 8 | ) 9 | 10 | const ( 11 | acme_account_partition = "acme/accounts" 12 | ) 13 | 14 | type ACMEAccountDAO struct { 15 | *kvstore.AferoDAO[*entities.ACMEAccount] 16 | } 17 | 18 | func NewACMEAccountDAO(params *datastore.Params[*entities.ACMEAccount]) (dao.ACMEAccountDAO, error) { 19 | if params.Partition == "" { 20 | params.Partition = acme_account_partition 21 | } 22 | aferoDAO, err := kvstore.NewAferoDAO(params) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &ACMEAccountDAO{ 27 | AferoDAO: aferoDAO, 28 | }, nil 29 | } 30 | 31 | // func (accountDAO *ACMEAccountDAO) Save(entity *entities.ACMEAccount) error { 32 | // return accountDAO.AferoDAO.Save(entity) 33 | // } 34 | 35 | // func (accountDAO *ACMEAccountDAO) Get(id uint64, CONSISTENCY_LEVEL int) (*entities.ACMEAccount, error) { 36 | // return accountDAO.AferoDAO.Get(id, CONSISTENCY_LEVEL) 37 | // } 38 | 39 | // func (accountDAO *ACMEAccountDAO) Delete(entity *entities.ACMEAccount) error { 40 | // return accountDAO.AferoDAO.Delete(entity) 41 | // } 42 | 43 | // func (accountDAO *ACMEAccountDAO) Count(CONSISTENCY_LEVEL int) (int, error) { 44 | // return accountDAO.AferoDAO.Count(CONSISTENCY_LEVEL) 45 | // } 46 | 47 | // func (accountDAO *ACMEAccountDAO) Page( 48 | // pageQuery datastore.PageQuery, 49 | // CONSISTENCY_LEVEL int) (datastore.PageResult[*entities.ACMEAccount], error) { 50 | 51 | // return accountDAO.AferoDAO.Page(pageQuery, CONSISTENCY_LEVEL) 52 | // } 53 | 54 | // func (accountDAO *ACMEAccountDAO) ForEachPage( 55 | // pageQuery datastore.PageQuery, 56 | // pagerProcFunc datastore.PagerProcFunc[*entities.ACMEAccount], 57 | // CONSISTENCY_LEVEL int) error { 58 | 59 | // return accountDAO.AferoDAO.ForEachPage(pageQuery, pagerProcFunc, CONSISTENCY_LEVEL) 60 | // } 61 | -------------------------------------------------------------------------------- /pkg/cmd/platform/install.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/certstore" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/tpm2" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var InstallCmd = &cobra.Command{ 11 | Use: "install", 12 | Short: "Safely provisions the platform", 13 | Long: `Perform a modified version of the TCG recommended provisioning 14 | guidance procedure, intended for platforms with a pre-provisioned TPM, either 15 | from the TPM Manufacturer or Owner. Instead of clearing the hierarchies, 16 | setting hierarchy authorizations and provisioning new keys and certificates 17 | from scratch, this operation will use pre-existing EK, Shared SRK and IAK keys 18 | and certificates if they already exist. The Security Officer PIN is required 19 | and used as Endorsement and Storage hierarchy authorization values during 20 | installation. This operation is safe and idempotent, and will not modify or 21 | destroy existing data.`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | 24 | var soPIN, userPIN keystore.Password 25 | if App.CA == nil { 26 | soPIN, userPIN, err = App.ParsePINs(InitParams.SOPin, InitParams.Pin) 27 | if err != nil { 28 | App.Logger.Error(err) 29 | cmd.PrintErrln(err) 30 | return 31 | } 32 | } 33 | 34 | App.OpenTPM(false) 35 | 36 | if err := App.TPM.Install(soPIN); err != nil { 37 | 38 | if err == tpm2.ErrEndorsementCertNotFound { 39 | 40 | if App.CA == nil { 41 | if _, err := App.InitCA(soPIN, userPIN, InitParams); err != nil { 42 | cmd.PrintErrln(err) 43 | return 44 | } 45 | } 46 | 47 | cert, err := App.ImportEndorsementKeyCertificate() 48 | if err != nil { 49 | cmd.PrintErrln(err) 50 | return 51 | } 52 | 53 | pem, err := certstore.EncodePEM(cert.Raw) 54 | if err != nil { 55 | cmd.PrintErrln(err) 56 | return 57 | } 58 | 59 | cmd.Println(string(pem)) 60 | 61 | } else { 62 | 63 | cmd.PrintErrln(err) 64 | return 65 | } 66 | } 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /pkg/store/keystore/signer_store.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | blobstore "github.com/jeremyhahn/go-trusted-platform/pkg/store/blob" 8 | ) 9 | 10 | type SignerStorer interface { 11 | Checksum(verifyOpts *VerifyOpts) ([]byte, error) 12 | SaveSignature(opts any, signature, digest []byte) error 13 | } 14 | 15 | type SignerStore struct { 16 | blobStore blobstore.BlobStorer 17 | SignerStorer 18 | } 19 | 20 | // Provides blob storage for signed data 21 | func NewSignerStore( 22 | blobStore blobstore.BlobStorer) SignerStorer { 23 | 24 | return SignerStore{ 25 | blobStore: blobStore, 26 | } 27 | } 28 | 29 | // Returns a stored checksum from the signed blob store 30 | func (ks SignerStore) Checksum(verifyOpts *VerifyOpts) ([]byte, error) { 31 | ext := HashFileExtension(verifyOpts.KeyAttributes.Hash) 32 | key := fmt.Sprintf("%s%s", verifyOpts.BlobCN, ext) 33 | return ks.blobStore.Get([]byte(key)) 34 | } 35 | 36 | // Signs and saves blobs passed to a signer as SignerOpts 37 | func (ks SignerStore) SaveSignature( 38 | opts any, 39 | signature, digest []byte) error { 40 | 41 | if opts, ok := opts.(*SignerOpts); ok { 42 | 43 | // Save the signature 44 | sigKey := fmt.Sprintf("%s%s", opts.BlobCN, FSEXT_SIG) 45 | if err := ks.blobStore.Save([]byte(sigKey), signature); err != nil { 46 | return err 47 | } 48 | 49 | // Save the digest 50 | digestKey := fmt.Sprintf("%s%s", opts.BlobCN, FSEXT_DIGEST) 51 | if err := ks.blobStore.Save([]byte(digestKey), digest); err != nil { 52 | return err 53 | } 54 | 55 | // Save a checksum 56 | checksum := hex.EncodeToString(digest) 57 | ext := FSExtension(HashFileExtension(opts.HashFunc())) 58 | checksumKey := fmt.Sprintf("%s%s", opts.BlobCN, ext) 59 | if err := ks.blobStore.Save([]byte(checksumKey), []byte(checksum)); err != nil { 60 | return err 61 | } 62 | 63 | // Save the data if set 64 | if opts.BlobData != nil { 65 | if err := ks.blobStore.Save([]byte(opts.BlobCN), opts.BlobData); err != nil { 66 | return err 67 | } 68 | } 69 | } else { 70 | return ErrInvalidSignerOpts 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/cmd/ca/info.go: -------------------------------------------------------------------------------- 1 | package ca 2 | 3 | import ( 4 | "github.com/jeremyhahn/go-trusted-platform/pkg/app" 5 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/prompt" 6 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var InfoCmd = &cobra.Command{ 11 | Use: "info", 12 | Short: "Display information about a Certificate Authority", 13 | Long: `Displays key store, certificate store, and general information 14 | about a Certificate Authority.`, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | 17 | prompt.PrintBanner(app.Version) 18 | 19 | App, err = App.Init(InitParams) 20 | if err != nil { 21 | cmd.PrintErrln(err) 22 | return 23 | } 24 | 25 | if App.CAConfig == nil || len(App.CAConfig.Identity) == 0 { 26 | App.Logger.Fatal("Certificate Authority not configured") 27 | } 28 | 29 | if App.CA == nil { 30 | soPIN, userPIN, err := App.ParsePINs(InitParams.SOPin, InitParams.Pin) 31 | if err != nil { 32 | App.Logger.Error(err) 33 | cmd.PrintErrln(err) 34 | return 35 | } 36 | if err := App.LoadCA(soPIN, userPIN); err != nil { 37 | cmd.PrintErrln(err) 38 | return 39 | } 40 | } 41 | 42 | initialized, err := App.CA.IsInitialized() 43 | if err != nil { 44 | cmd.PrintErrln(err) 45 | return 46 | } 47 | 48 | cmd.Printf("Initialized: %t\n", initialized) 49 | 50 | identity := App.CAConfig.Identity[App.CAConfig.PlatformCA] 51 | 52 | // Iterate over the configured keys 53 | for _, keyConfig := range identity.Keys { 54 | 55 | // Parse the store type and key algorithm 56 | storeType, err := keystore.ParseStoreType(keyConfig.StoreType) 57 | if err != nil { 58 | cmd.PrintErrln(err) 59 | return 60 | } 61 | keyAlgorithm, err := keystore.ParseKeyAlgorithm(keyConfig.KeyAlgorithm) 62 | if err != nil { 63 | cmd.PrintErrln(err) 64 | return 65 | } 66 | // Get the loaded key attributes 67 | attrs, err := App.CA.CAKeyAttributes(storeType, keyAlgorithm) 68 | if err != nil { 69 | cmd.PrintErrln(err) 70 | return 71 | } 72 | cmd.Println(attrs) 73 | } 74 | 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /pkg/acme/server/handlers/account.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/jeremyhahn/go-trusted-platform/pkg/acme" 8 | ) 9 | 10 | // AccountRequest represents the structure of the incoming account update/deactivation request body. 11 | type AccountRequest struct { 12 | Status string `json:"status,omitempty"` 13 | Contact []string `json:"contact,omitempty"` 14 | } 15 | 16 | func (s *RestService) AccountHandler(w http.ResponseWriter, r *http.Request) { 17 | 18 | s.logger.Debug("AccountHandler", "method", r.Method, "url", r.URL) 19 | for name, values := range r.Header { 20 | for _, value := range values { 21 | s.logger.Debugf("%s: %s\n", name, value) 22 | } 23 | } 24 | 25 | account, payload, err := s.parseKID(r) 26 | if err != nil { 27 | writeError(w, acme.MalformedError("Failed to parse account key", nil)) 28 | return 29 | } 30 | 31 | accountDAO, err := s.params.DAOFactory.ACMEAccountDAO() 32 | if err != nil { 33 | writeError(w, acme.ServerInternal("Failed to create account DAO")) 34 | return 35 | } 36 | 37 | var accountRequest AccountRequest 38 | if err := json.Unmarshal(payload, &accountRequest); err != nil { 39 | writeError(w, acme.MalformedError("Invalid request payload", nil)) 40 | return 41 | } 42 | 43 | if accountRequest.Status != "" { 44 | if accountRequest.Status == acme.StatusDeactivated { 45 | if account.Status == acme.StatusDeactivated { 46 | writeError(w, acme.MalformedError("Account is already deactivated", nil)) 47 | return 48 | } 49 | account.Status = acme.StatusDeactivated 50 | } else { 51 | writeError(w, acme.MalformedError("Invalid status value", nil)) 52 | return 53 | } 54 | } 55 | 56 | if accountRequest.Contact != nil { 57 | if err := validateContactURLs(accountRequest.Contact); err != nil { 58 | writeError(w, err) 59 | return 60 | } 61 | account.Contact = accountRequest.Contact 62 | } 63 | 64 | if err := accountDAO.Save(account); err != nil { 65 | writeError(w, acme.ServerInternal("Failed to update account")) 66 | return 67 | } 68 | 69 | s.respondWithAccount(w, account, http.StatusOK) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/platform/auth/yubikey.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/GeertJohan/yubigo" 10 | "github.com/jeremyhahn/go-trusted-platform/pkg/logging" 11 | "github.com/jeremyhahn/go-trusted-platform/pkg/platform/prompt" 12 | "github.com/jeremyhahn/go-trusted-platform/pkg/store/keystore" 13 | ) 14 | 15 | type YubiAuth struct { 16 | logger *logging.Logger 17 | client *yubigo.YubiAuth 18 | PlatformAuthenticator 19 | } 20 | 21 | func NewYubiKeyAuthenticator( 22 | logger *logging.Logger, 23 | clientID string, 24 | secretKey keystore.Password) (PlatformAuthenticator, error) { 25 | 26 | var secret string 27 | var err error 28 | 29 | if secretKey != nil { 30 | secret, err = secretKey.String() 31 | if err != nil { 32 | return nil, err 33 | } 34 | } 35 | 36 | yubigo.HTTPClient = &http.Client{ 37 | Timeout: time.Second * 10, 38 | Transport: &http.Transport{ 39 | MaxConnsPerHost: 20, 40 | MaxIdleConnsPerHost: 5, 41 | DialContext: (&net.Dialer{ 42 | Timeout: 30 * time.Second, 43 | KeepAlive: 60 * time.Second, 44 | }).DialContext, 45 | TLSHandshakeTimeout: 10 * time.Second, 46 | ResponseHeaderTimeout: 10 * time.Second, 47 | ExpectContinueTimeout: 1 * time.Second, 48 | }, 49 | } 50 | 51 | client, err := yubigo.NewYubiAuth(clientID, secret) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return &YubiAuth{ 57 | logger: logger, 58 | client: client, 59 | }, nil 60 | } 61 | 62 | func (yubiauth *YubiAuth) Prompt() []byte { 63 | fmt.Println("Touch your YubiKey to send an OTP code...") 64 | fmt.Println() 65 | return prompt.PasswordPrompt("YubiKey OTP") 66 | } 67 | 68 | func (yubiauth *YubiAuth) Authenticate(otp []byte) error { 69 | result, ok, err := yubiauth.client.Verify(string(otp)) 70 | if err != nil { 71 | yubiauth.logger.Error(err) 72 | return err 73 | } 74 | if !ok { 75 | yubiauth.logger.Error(ErrAuthenticationFailed) 76 | return ErrAuthenticationFailed 77 | } 78 | tokenID := string(otp[:len(otp)-32]) 79 | yubiauth.logger.Debugf("yubiauth: id: %s", tokenID) 80 | yubiauth.logger.Debugf("yubiauth: result: %s", result) 81 | return nil 82 | } 83 | --------------------------------------------------------------------------------