File: Creating%20Certificates.md

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (180 lines) | stat: -rw-r--r-- 7,579 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# Creating Certificates

Users often have a need to create certificates. Whether for testing or some other interaction with an
existing system, creating a certificate programmatically is a powerful tool for users.

``X509`` provides a number of conveniences for making this easy. There are two common ways to create
a certificate: directly, or from a CSR.

## Creating Certificates Directly

There are many kinds of certificates that users may want to create, but the most common is to want
to create a self-signed certificate. These are valuable in testing scenarios, as they can serve
both as a trusted root and as an end-entity certificate.

Fortunately, creating a self-signed certificate is very similar to creating an intermediate or leaf
certificate. This document will call out the steps that might need to be different.

### Gathering our requirements

To create a certificate directly we're going to call the
``Certificate/init(version:serialNumber:publicKey:notValidBefore:notValidAfter:issuer:subject:signatureAlgorithm:extensions:issuerPrivateKey:)``
initializer. This requires that we put together the following information:

- Version
- Serial number
- Validity period
- Subject name
- Issuer name
- Extensions
- Signature algorithm
- Public Key
- Issuer private key

### Version

Working out the version to use is easy. When creating our own certificates, we should always create a
``Certificate/Version-swift.struct/v3`` certificate. There is no reason to use any version older than this.

### Serial Number

The certificate serial number forms part of the identifier of the certificate, along with the issuer name. For a given
CA, each certificate it issues must have a unique serial number. Additionally, the
[CAB Forum Baseline Requirements](https://cabforum.org/baseline-requirements-documents/) require that for certificates that
need to conform to this profile the serial number must contain at least 64-bits of output from a CSPRNG.

``Certificate/SerialNumber-swift.struct`` has a helper initializer that will create a 20-byte serial number
consisting entirely of randomness: ``Certificate/SerialNumber-swift.struct/init()``. That's a good default choice for
this kind of use-case.

### Validity Period

The validity period of a certificate is made up of two dates: "not valid before" and "not valid after". The "not valid before" date
refers to the point in time before which a certificate must not be trusted, and is typically set to the date and time at which
the certificate was signed by the issuer. The "not valid after" date refers to the point in time after which the certificate has
expired and should not be trusted.

Together these two dates define a validity period. We'll set "not valid before" to the current time (`Date()`), and we'll set the
certificate to be valid for a year, making the "not valid after" date `now.addingTimeInterval(60 * 60 * 24 * 365)`.

### Subject Name

The subject name identifies the entity to whom the certificate is being issued. This is a hierarchical name type called a
``DistinguishedName``. The full complexity of distinguished names is tackled in the ``DistinguishedName`` API documentation,
but for our use-case it's sufficient to know that we don't need to set anything other than the common name. Additionally, in
all modern use-cases the common name is nothing more than an identifier, so we can set it to whatever we like.

```swift
let subjectName = DistinguishedName {
    CommonName("My awesome subject")
}
```

### Issuer Name

Just as the subject name identifies the entity to whom the subject is being issued, the issuer name identifies the entity that
issued the certificate. In particular, the issuer name should be exactly equivalent to the subject name in the issuing entity's
certificate.

If we were creating a non-self-signed certificate, we'd set `issuerName` equal to ``Certificate/subject`` from the parent
certificate. For self-signed certificates, the issuer and the subject are identical, so we can set `issuerName = subjectName`.

### Extensions

The bulk of the semantic information in a certificate is contained in its extensions. For our case, we care about only a small
few.

We need ``BasicConstraints`` to be present, and set to
`isCertificateAuthority`. We also need ``KeyUsage`` with the appropriate bits
set. Finally, we want to set ``SubjectAlternativeNames`` to include the domain
name we're going to be self-signing for, which in this case we'll set to `localhost`.

We can use the helpful builder syntax for this:

```swift
let extensions = try Certificate.Extensions {
    Critical(
        BasicConstraints.isCertificateAuthority(maxPathLength: nil)
    )
    Critical(
        KeyUsage(digitalSignature: true, keyCertSign: true)
    )
    SubjectAlternativeNames([.dnsName("localhost")])
}
```

### Cryptographic Material

In our case, the public key, signature algorithm, and issuer private key are intimately bound together. For self-signed certs, the
public key is the public key that belongs to the issuer private key. Relatedly, the signature algorithm is constrained to only those
that are supported by our private key, as that'll be the one doing the signing.

If we weren't creating a self-signed certificate, the issuer private key would be the private key for the issuing certificate,
and the signature algorithm would be constrained to what that key is capable of. The public key could be anything, but it needs to
match the private key that the subject entity has attested to possessing.

We can use the keys from `swift-crypto` for this operation. We'll select `P256.Signing.PrivateKey` as our private key, which
we can wrap up in ``Certificate/PrivateKey/init(_:)-2we15`` to get `issuerPrivateKey`. We can then derive `publicKey` via
``Certificate/PrivateKey/publicKey``. Finally, we'll pick the only signature algorithm compatible with that key, which is
``Certificate/SignatureAlgorithm-swift.struct/ecdsaWithSHA256``.

### Serializing

Once the certificate is created, we'll want to write it out to a file so we can use it! We can easily output this in DER format,
which is commonly indicated using a `.crt` file extension.

We get the DER representation by using code from `SwiftASN1`. ``Certificate`` conforms to `DERSerializable`, so we can serialize
it like this:

```swift
var serializer = DER.Serializer()
try serializer.serialize(certificate)
```

### Putting it all together

That leaves us with the following code for generating a self-signed certificate.

```swift
import Crypto
import SwiftASN1
import X509

let swiftCryptoKey = P256.Signing.PrivateKey()
let key = Certificate.PrivateKey(swiftCryptoKey)

let subjectName = try DistinguishedName {
    CommonName("My awesome subject")
}
let issuerName = subjectName

let now = Date()

let extensions = try Certificate.Extensions {
    Critical(
        BasicConstraints.isCertificateAuthority(maxPathLength: nil)
    )
    Critical(
        KeyUsage(keyCertSign: true)
    )
    SubjectAlternativeNames([.dnsName("localhost")])
}

let certificate = try Certificate(
    version: .v3,
    serialNumber: Certificate.SerialNumber(),
    publicKey: key.publicKey,
    notValidBefore: now,
    notValidAfter: now.addingTimeInterval(60 * 60 * 24 * 365),
    issuer: issuerName,
    subject: subjectName,
    signatureAlgorithm: .ecdsaWithSHA256,
    extensions: extensions,
    issuerPrivateKey: key)

var serializer = DER.Serializer()
try serializer.serialize(certificate)

let derEncodedCertificate = serializer.serializedBytes
let derEncodedPrivateKey = swiftCryptoKey.derRepresentation
```