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