-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathcert.go
More file actions
157 lines (135 loc) · 5.44 KB
/
cert.go
File metadata and controls
157 lines (135 loc) · 5.44 KB
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
package keycred
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math"
"math/big"
"time"
mathrand "math/rand"
"github.com/RedTeamPentesting/adauth/x509ext"
"github.com/google/uuid"
"software.sslmate.com/src/go-pkcs12"
)
// Credential hols both the KeyCredentialLink as well as the corresponding
// certificate and private key, including the PFX bundle.
type Credential struct {
KeyCredentialLink *KeyCredentialLink
Key *rsa.PrivateKey
Certificate *x509.Certificate
PFX []byte
}
// GeneratePFXAndKeyCredentialLink generates a certificate and private key
// alongside the corresponding KeyCredentialLink with the required entries as
// well as a device ID, stub custom key information, an approximate last logon
// time stamp with the current time and a key creation time with the current
// time. The subject is reflected in the certificate common name, the DN in the
// KeyCredentialLink's DN-Binary representation. Optionally, otherName can be
// supplied which will be reflected in an otherName certificate SAN extension if it
// is not empty.
func GeneratePFXAndKeyCredentialLink(
keySize int, subject string, dn string, otherName string, deviceID uuid.UUID, pfxPassword string,
) (*Credential, error) {
additionalEntries := []KeyCredentialLinkEntry{
NewKeySourceEntry(KeySourceAD),
NewDeviceIDEntry(deviceID),
NewCustomKeyInformationEntry(nil),
NewKeyApproximateLastLogonTimeStampEntry(time.Now()),
NewKeyCreationTimeEntry(time.Now()),
}
return GeneratePFXAndCustomKeyCredentialLink(
keySize, subject, dn, otherName, false, pfxPassword, additionalEntries...)
}
// GeneratePFXAndKeyCredentialLink generates a certificate and private key
// alongside the corresponding KeyCredentialLink that only contains the entries
// that are compatible with validated writes
// (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f70afbcc-780e-4d91-850c-cfadce5bb15c).
// The subject is reflected in the certificate common name, the DN in the
// KeyCredentialLink's DN-Binary representation. Optionally, otherName can be
// supplied which will be reflected in an otherName certificate SAN extension if
// it is not empty.
func GeneratePFXAndValidatedWriteCompatibleKeyCredentialLink(
keySize int, subject string, dn string, otherName string, deviceID uuid.UUID, pfxPassword string,
) (*Credential, error) {
additionalEntries := []KeyCredentialLinkEntry{
NewKeySourceEntry(KeySourceAD),
NewDeviceIDEntry(deviceID),
NewKeyCreationTimeEntry(time.Now()),
}
cred, err := GeneratePFXAndCustomKeyCredentialLink(
keySize, subject, dn, otherName, true, pfxPassword, additionalEntries...)
if err != nil {
return nil, err
}
err = cred.KeyCredentialLink.CheckValidatedWriteCompatible()
if err != nil {
return nil, fmt.Errorf("generated KeyCredentialLink is not compatible for validated writes: %w", err)
}
return cred, nil
}
// GeneratePFXAndKeyCredentialLink generates a certificate and private key
// alongside the corresponding KeyCredentialLink with custom key format, the
// required entries and user-supplied additional entries. The subject is
// reflected in the certificate common name, the DN in the KeyCredentialLink's
// DN-Binary representation. Optionally, otherName can be supplied which will be
// reflected in an otherName certificate SAN extension if it is not empty.
func GeneratePFXAndCustomKeyCredentialLink(
keySize int, subject string, dn string, otherName string,
derFormatted bool, pfxPassword string, additionalEntries ...KeyCredentialLinkEntry,
) (*Credential, error) {
key, cert, err := createKeyAndCert(keySize, subject, otherName)
if err != nil {
return nil, fmt.Errorf("parse certificate: %w", err)
}
kcl, err := newKeyCredentialLink(&key.PublicKey, dn, KeyUsageNGC, derFormatted, additionalEntries...)
if err != nil {
return nil, fmt.Errorf("generate KeyCredentialLink: %w", err)
}
pfxEncoder := pkcs12.Passwordless
if pfxPassword != "" {
pfxEncoder = pkcs12.Modern
}
pfx, err := pfxEncoder.Encode(key, cert, nil, pfxPassword)
if err != nil {
return nil, fmt.Errorf("build PFX: %w", err)
}
return &Credential{
KeyCredentialLink: kcl,
Key: key,
Certificate: cert,
PFX: pfx,
}, nil
}
func createKeyAndCert(keySize int, subject string, otherName string) (*rsa.PrivateKey, *x509.Certificate, error) {
key, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return nil, nil, fmt.Errorf("generate key: %w", err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(int64(mathrand.Intn(math.MaxInt))),
Issuer: pkix.Name{CommonName: subject},
Subject: pkix.Name{CommonName: subject},
KeyUsage: x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
NotBefore: time.Now().Add(-40 * 365 * 24 * time.Hour),
NotAfter: time.Now().Add(40 * 365 * 24 * time.Hour),
}
if otherName != "" {
otherNameExtension, err := x509ext.NewOtherNameExtensionFromUPNs(otherName)
if err != nil {
return nil, nil, fmt.Errorf("generate otherName extension: %w", err)
}
template.ExtraExtensions = append(template.ExtraExtensions, otherNameExtension)
}
certDer, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
return nil, nil, fmt.Errorf("sign certificate: %w", err)
}
cert, err := x509.ParseCertificate(certDer)
if err != nil {
return nil, nil, fmt.Errorf("parse certificate: %w", err)
}
return key, cert, nil
}