blob: e6d1a010fd533fa0498418960a2c7773b05f6d99 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanski551a8192024-06-04 14:32:11 +00004package main
5
6// swtpm_cert (from swtpm project) reimplemented in Go.
7//
8// This tool generates a TPM EK or Platform certificate. These certificates have
9// to be in a very specific format and include non-standard extensions and
10// subjectAltNames.
11
12import (
13 "crypto/ecdsa"
14 "crypto/elliptic"
15 "crypto/rand"
16 "crypto/rsa"
17 "crypto/x509"
18 "crypto/x509/pkix"
19 "encoding/asn1"
20 "encoding/hex"
21 "encoding/pem"
22 "log"
23 "math/big"
24 "os"
25 "strings"
26 "time"
27
28 "github.com/spf13/pflag"
29
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020030 "source.monogon.dev/osbase/pki"
Serge Bazanski551a8192024-06-04 14:32:11 +000031)
32
33func getSignkey() *rsa.PrivateKey {
34 if strings.HasPrefix(flagSignKey, "tpmkey:") || strings.HasPrefix(flagSignKey, "pkcs11:") {
35 log.Fatalf("Loading tpmkey: and pkcs11: sign keys unimplemented")
36 }
37 bytes, err := os.ReadFile(flagSignKey)
38 if err != nil {
39 log.Fatalf("Could not read private key: %v", err)
40 }
41 block, _ := pem.Decode(bytes)
42 if block.Type != "RSA PRIVATE KEY" {
43 log.Fatalf("Private key contains invalid PEM data")
44 }
45 res, err := x509.ParsePKCS1PrivateKey(block.Bytes)
46 if err != nil {
47 log.Fatalf("Could not parse private key: %v", err)
48 }
49 return res
50}
51
52func getPubkey() any {
53 if flagModulus != "" {
54 if flagECCX != "" || flagECCY != "" || flagECCCurveID != "" {
55 log.Fatalf("--modulus and --ecc* cannot be set simultaneously")
56 }
57 var modulus big.Int
58 modulusBytes, err := hex.DecodeString(flagModulus)
59 if err != nil {
60 log.Fatalf("Could not decode modulus: %v", err)
61 }
62 modulus.SetBytes(modulusBytes)
63 return &rsa.PublicKey{
64 N: &modulus,
65 E: flagExponent,
66 }
67 }
68 if flagECCX != "" && flagECCY != "" && flagECCCurveID != "" {
69 if flagModulus != "" {
70 log.Fatalf("--modulus and --ecc* cannot be set simultaneously")
71 }
72 var x, y big.Int
73 xBytes, err := hex.DecodeString(flagECCX)
74 if err != nil {
75 log.Fatalf("Could not decode ECC X: %v", err)
76 }
77 x.SetBytes(xBytes)
78 yBytes, err := hex.DecodeString(flagECCY)
79 if err != nil {
80 log.Fatalf("Could not decode ECC Y: %v", err)
81 }
82 y.SetBytes(yBytes)
83 res := ecdsa.PublicKey{X: &x, Y: &y}
84 switch flagECCCurveID {
85 case "secp256r1":
86 res.Curve = elliptic.P256()
87 case "secp384r1":
88 res.Curve = elliptic.P384()
89 default:
90 log.Fatalf("Unknown ECC curve ID %q", flagECCCurveID)
91 }
92 return &res
93 }
94 log.Fatalf("--modulus or --ecc* must be set")
95 panic("unreachable")
96}
97
98func getIssuerCert() *x509.Certificate {
99 bytes, err := os.ReadFile(flagIssuerCert)
100 if err != nil {
101 log.Fatalf("Could not read issuer certificate: %v", err)
102 }
103 block, _ := pem.Decode(bytes)
104 if block.Type != "CERTIFICATE" {
105 log.Fatalf("Issuer certificate contains invalid PEM data")
106 }
107 res, err := x509.ParseCertificate(block.Bytes)
108 if err != nil {
109 log.Fatalf("Could not parse issuer certificate: %v", err)
110 }
111 return res
112}
113
114type certType string
115
116const (
117 certTypeEK certType = "ek"
118 certTypePlatform certType = "platform"
119)
120
121var (
122 flagType string
123 flagSubject string
124 flagPlatformManufacturer string
125 flagPlatformVersion string
126 flagPlatformModel string
127 flagTPM2 bool
128 flagTPMSpecFamily string
129 flagTPMSpecLevel int
130 flagTPMSpecRevision int
131 flagTPMManufacturer string
132 flagTPMModel string
133 flagTPMVersion string
134 flagOutCert string
135 flagExponent int
136 flagSignKey string
137 flagIssuerCert string
138 flagDays int
139 flagSerial string
140
141 flagModulus string
142
143 flagECCX string
144 flagECCY string
145 flagECCCurveID string
146)
147
148func main() {
149 pflag.BoolVar(&flagTPM2, "tpm2", false, "Enable TPM2 mode (no-op, only mode supported)")
150 pflag.StringVar(&flagType, "type", "ek", "Type of certificate to create, ek or platform")
151
152 pflag.StringVar(&flagPlatformManufacturer, "platform-manufacturer", "", "TPM platform manufacturer")
153 pflag.StringVar(&flagPlatformVersion, "platform-version", "", "TPM platform version")
154 pflag.StringVar(&flagPlatformModel, "platform-model", "", "TPM platform model")
155
156 pflag.StringVar(&flagTPMSpecFamily, "tpm-spec-family", "", "TPM Specification family")
157 pflag.IntVar(&flagTPMSpecLevel, "tpm-spec-level", -1, "TPM Specification level")
158 pflag.IntVar(&flagTPMSpecRevision, "tpm-spec-revision", -1, "TPM Specification revision")
159
160 pflag.StringVar(&flagTPMManufacturer, "tpm-manufacturer", "", "TPM device manufacturer")
161 pflag.StringVar(&flagTPMModel, "tpm-model", "", "TPM device model")
162 pflag.StringVar(&flagTPMVersion, "tpm-version", "", "TPM device version")
163
164 pflag.StringVar(&flagSubject, "subject", "", "Certificate subject (only cn=... is implemented)")
165 pflag.IntVar(&flagDays, "days", 0, "")
166 pflag.StringVar(&flagSerial, "serial", "", "")
167
168 pflag.StringVar(&flagOutCert, "out-cert", "", "Path to generated certificate (.pem)")
169 pflag.StringVar(&flagIssuerCert, "issuercert", "", "Path to issuer certificate (.pem)")
170 pflag.StringVar(&flagSignKey, "signkey", "", "Path to private key used to sign certificate")
171
172 pflag.IntVar(&flagExponent, "exponent", 0x10001, "RSA key exponent")
173 pflag.StringVar(&flagModulus, "modulus", "", "RSA key modulus")
174
175 pflag.StringVar(&flagECCX, "ecc-x", "", "ECC key x component")
176 pflag.StringVar(&flagECCY, "ecc-y", "", "ECC key y component")
177 pflag.StringVar(&flagECCCurveID, "ecc-curveid", "", "ECC curve id (one of secp256r1, secp384r1)")
178 pflag.Parse()
179
180 var ty certType
181 switch flagType {
182 case "ek":
183 ty = certTypeEK
184 case "platform":
185 ty = certTypePlatform
186 default:
187 log.Fatalf("Unknown type %q (must be ek or platform)", flagType)
188 }
189 if ty == certTypeEK || ty == certTypePlatform {
190 if flagTPMManufacturer == "" {
191 log.Fatalf("--tpm-manufacturer must be set")
192 }
193 if flagTPMModel == "" {
194 log.Fatalf("--tpm-model must be set")
195 }
196 if flagTPMVersion == "" {
197 log.Fatalf("--tpm-version must be set")
198 }
199 }
200 if ty == certTypeEK {
201 if flagTPMSpecFamily == "" {
202 log.Fatalf("--tpm-spec-family must be set")
203 }
204 if flagTPMSpecLevel == -1 {
205 log.Fatalf("--tpm-spec-level must be set")
206 }
207 if flagTPMSpecRevision == -1 {
208 log.Fatalf("--tpm-spec-revision must be set")
209 }
210 }
211 if ty == certTypePlatform {
212 if flagPlatformManufacturer == "" {
213 log.Fatalf("--platform-manufacturer must be set")
214 }
215 if flagPlatformModel == "" {
216 log.Fatalf("--platform-model must be set")
217 }
218 if flagPlatformVersion == "" {
219 log.Fatalf("--platform-version must be set")
220 }
221 }
222
223 pubkey := getPubkey()
224 signkey := getSignkey()
225 issuercert := getIssuerCert()
226
227 var cert x509.Certificate
228 cert.Version = 3
229 cert.SerialNumber = big.NewInt(0)
230 if _, ok := cert.SerialNumber.SetString(flagSerial, 10); !ok {
231 log.Fatalf("Could not parse serial %q", flagSerial)
232 }
233 cert.NotBefore = time.Now()
234 if flagDays > 0 {
235 cert.NotAfter = time.Now().Add(time.Hour * 24 * time.Duration(flagDays))
236 } else {
237 cert.NotAfter = pki.UnknownNotAfter
238 }
239 if flagSubject != "" {
240 parts := strings.Split(flagSubject, ",")
241 for _, part := range parts {
242 part = strings.TrimSpace(part)
243 els := strings.SplitN(part, "=", 2)
244 k := strings.ToLower(els[0])
245 switch k {
246 case "cn":
247 cert.Subject.CommonName = els[1]
248 default:
249 log.Fatalf("Unparseable subject: %q", flagSubject)
250 }
251 }
252 }
253 var sanValues = []asn1.RawValue{}
254 switch ty {
255 case certTypeEK:
256 sanValues = append(sanValues, asn1.RawValue{
257 Tag: 4, Class: 2, Bytes: buildManufacturerInfo(flagTPMManufacturer, flagTPMModel, flagTPMVersion),
258 })
259 case certTypePlatform:
260 sanValues = append(sanValues, asn1.RawValue{
261 Tag: 4, Class: 2, Bytes: buildPlatformManufacturerInfo(flagPlatformManufacturer, flagPlatformModel, flagPlatformVersion),
262 })
263 }
264 sanBytes, err := asn1.Marshal(sanValues)
265 if err != nil {
266 log.Fatalf("Failed to marshal SAN values: %v", err)
267 }
268 cert.ExtraExtensions = []pkix.Extension{
269 {
270 Id: asn1.ObjectIdentifier{2, 5, 29, 17}, // subjectAltName
271 Value: sanBytes,
272 },
273 }
274 if ty == certTypeEK {
275 cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
276 Id: asn1.ObjectIdentifier{2, 5, 29, 9}, // directoryName
277 Value: buildSpecificationInfo(flagTPMSpecFamily, flagTPMSpecLevel, flagTPMSpecRevision),
278 })
279 }
280 cert.BasicConstraintsValid = true
281 cert.IsCA = false
282 switch ty {
283 case certTypeEK:
284 // tcg-kp-EKCertificate
285 cert.UnknownExtKeyUsage = []asn1.ObjectIdentifier{{2, 23, 133, 8, 1}}
286 case certTypePlatform:
287 // tcg-kp-PlatformAttributeCertificate
288 cert.UnknownExtKeyUsage = []asn1.ObjectIdentifier{{2, 23, 133, 8, 2}}
289 }
290
291 derBytes, err := x509.CreateCertificate(rand.Reader, &cert, issuercert, pubkey, signkey)
292 if err != nil {
293 log.Fatalf("Generating certificate failed: %v", err)
294 }
295 block := pem.Block{
296 Type: "CERTIFICATE",
297 Bytes: derBytes,
298 }
299 if err := os.WriteFile(flagOutCert, pem.EncodeToMemory(&block), 0644); err != nil {
300 log.Fatalf("Writing certificate failed: %v", err)
301 }
302}