Skip to content

Commit 8c3482f

Browse files
committed
Updated certmanager
1 parent d91ad61 commit 8c3482f

File tree

8 files changed

+415
-12
lines changed

8 files changed

+415
-12
lines changed

pkg/cert/cert.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
// Packages
1616
schema "github.com/mutablelogic/go-server/pkg/cert/schema"
17-
"github.com/mutablelogic/go-server/pkg/types"
17+
types "github.com/mutablelogic/go-server/pkg/types"
1818
)
1919

2020
////////////////////////////////////////////////////////////////////////////////
@@ -88,11 +88,16 @@ func New(opts ...Opt) (*Cert, error) {
8888

8989
// Set the name from the common name
9090
cert.Name = cert.x509.Subject.CommonName
91+
if cert.Name == "" {
92+
cert.Name = fmt.Sprintf("%x", cert.x509.SerialNumber)
93+
}
9194

9295
// Create the certificate
9396
signer := cert.Signer
9497
if signer == nil {
9598
signer = cert
99+
} else {
100+
cert.x509.Issuer = signer.x509.Subject
96101
}
97102
if data, err := x509.CreateCertificate(rand.Reader, &cert.x509, &signer.x509, cert.PublicKey(), signer.priv); err != nil {
98103
return nil, err
@@ -144,6 +149,11 @@ func Read(r io.Reader) (*Cert, error) {
144149
data = rest
145150
}
146151

152+
// Set name from serial number if not set
153+
if cert.Name == "" {
154+
cert.Name = fmt.Sprintf("%x", cert.x509.SerialNumber)
155+
}
156+
147157
// Return success
148158
return cert, nil
149159
}
@@ -169,10 +179,11 @@ func (c Cert) String() string {
169179
// Return metadata from a cert
170180
func (c Cert) Meta() schema.CertMeta {
171181
signerNamePtr := func() *string {
172-
if c.Signer == nil {
182+
if c.Signer != nil {
183+
return types.StringPtr(c.Signer.Name)
184+
} else {
173185
return nil
174186
}
175-
return types.StringPtr(c.Signer.Name)
176187
}
177188
return schema.CertMeta{
178189
Name: c.Name,
@@ -189,6 +200,27 @@ func (c Cert) Meta() schema.CertMeta {
189200
}
190201
}
191202

203+
// Return metadata from a cert
204+
func (c Cert) SubjectMeta() schema.NameMeta {
205+
fieldPtr := func(field []string) *string {
206+
if len(field) > 0 {
207+
return types.StringPtr(field[0])
208+
} else {
209+
return nil
210+
}
211+
}
212+
return schema.NameMeta{
213+
CommonName: c.x509.Subject.CommonName,
214+
Org: fieldPtr(c.x509.Subject.Organization),
215+
Unit: fieldPtr(c.x509.Subject.OrganizationalUnit),
216+
Country: fieldPtr(c.x509.Subject.Country),
217+
City: fieldPtr(c.x509.Subject.Locality),
218+
State: fieldPtr(c.x509.Subject.Province),
219+
StreetAddress: fieldPtr(c.x509.Subject.StreetAddress),
220+
PostalCode: fieldPtr(c.x509.Subject.PostalCode),
221+
}
222+
}
223+
192224
// Return true if the certificate is a certificate authority
193225
func (c *Cert) IsCA() bool {
194226
return c.x509.IsCA

pkg/cert/cert_test.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func Test_Cert_002(t *testing.T) {
4747
assert := assert.New(t)
4848

4949
ca, err := cert.New(
50+
cert.WithCommonName("CA"),
5051
cert.WithRSAKey(0),
5152
cert.WithExpiry(time.Hour),
5253
cert.WithCA(),
@@ -60,6 +61,7 @@ func Test_Cert_002(t *testing.T) {
6061
cert, err := cert.New(
6162
cert.WithRSAKey(0),
6263
cert.WithExpiry(time.Hour),
64+
cert.WithSigner(ca),
6365
)
6466
if assert.NoError(err) {
6567
assert.NoError(cert.Write(&data))
@@ -73,17 +75,26 @@ func Test_Cert_002(t *testing.T) {
7375

7476
t.Run("2", func(t *testing.T) {
7577
var data bytes.Buffer
76-
cert, err := cert.New(
78+
cert1, err := cert.New(
7779
cert.WithRSAKey(0),
7880
cert.WithExpiry(time.Hour),
81+
cert.WithSigner(ca),
7982
)
80-
if assert.NoError(err) {
81-
assert.NoError(cert.Write(&data))
82-
t.Log(data.String())
83+
if !assert.NoError(err) {
84+
t.FailNow()
8385
}
84-
if assert.NoError(err) {
85-
assert.NoError(cert.WritePrivateKey(&data))
86-
t.Log(data.String())
86+
// Write the certificate and key
87+
assert.NoError(cert1.Write(&data))
88+
assert.NoError(cert1.WritePrivateKey(&data))
89+
90+
// Read back into another cert
91+
cert2, err := cert.Read(&data)
92+
if !assert.NoError(err) {
93+
t.FailNow()
8794
}
95+
assert.NotNil(cert2)
96+
t.Log(cert1.String())
97+
t.Log(cert2.String())
98+
assert.Equal(cert1.String(), cert2.String())
8899
})
89100
}

pkg/cert/certmanager.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cert
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
// Packages
8+
pg "github.com/djthorpe/go-pg"
9+
schema "github.com/mutablelogic/go-server/pkg/cert/schema"
10+
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
11+
)
12+
13+
////////////////////////////////////////////////////////////////////////////////
14+
// TYPES
15+
16+
type CertManager struct {
17+
conn pg.PoolConn
18+
root *Cert
19+
}
20+
21+
////////////////////////////////////////////////////////////////////////////////
22+
// LIFECYCLE
23+
24+
// Create a new certificate manager, with a root certificate authority
25+
func NewCertManager(ctx context.Context, conn pg.PoolConn, opt ...Opt) (*CertManager, error) {
26+
self := new(CertManager)
27+
self.conn = conn.With("schema", schema.SchemaName).(pg.PoolConn)
28+
29+
// Create the root certificate from options
30+
if root, err := New(opt...); err != nil {
31+
return nil, err
32+
} else if !root.IsCA() {
33+
return nil, httpresponse.ErrInternalError.With("root certificate must be a certificate authority")
34+
} else {
35+
self.root = root
36+
}
37+
38+
// If the schema does not exist, then bootstrap it
39+
if err := self.conn.Tx(ctx, func(conn pg.Conn) error {
40+
if exists, err := pg.SchemaExists(ctx, conn, schema.SchemaName); err != nil {
41+
return err
42+
} else if !exists {
43+
return schema.Bootstrap(ctx, conn)
44+
}
45+
return nil
46+
}); err != nil {
47+
return nil, err
48+
}
49+
50+
// Register the root certificate
51+
if err := self.conn.Tx(ctx, func(conn pg.Conn) error {
52+
if name, err := self.RegisterName(ctx, self.root.SubjectMeta()); err != nil {
53+
return err
54+
} else {
55+
log.Println(name)
56+
}
57+
return nil
58+
}); err != nil {
59+
return nil, err
60+
}
61+
62+
// Return success
63+
return self, nil
64+
}
65+
66+
////////////////////////////////////////////////////////////////////////////////
67+
// PUBLIC METHODS
68+
69+
func (certmanager *CertManager) RegisterName(ctx context.Context, meta schema.NameMeta) (*schema.Name, error) {
70+
var name schema.Name
71+
if err := certmanager.conn.Insert(ctx, &name, meta); err != nil {
72+
return nil, err
73+
} else {
74+
return &name, nil
75+
}
76+
}
77+
78+
func (certmanager *CertManager) GetName(ctx context.Context, id uint64) (*schema.Name, error) {
79+
var name schema.Name
80+
if err := certmanager.conn.Get(ctx, &name, schema.NameId(id)); err != nil {
81+
return nil, err
82+
} else {
83+
return &name, nil
84+
}
85+
}
86+
87+
func (certmanager *CertManager) UpdateName(ctx context.Context, id uint64, meta schema.NameMeta) (*schema.Name, error) {
88+
var name schema.Name
89+
if err := certmanager.conn.Update(ctx, &name, schema.NameId(id), meta); err != nil {
90+
return nil, err
91+
} else {
92+
return &name, nil
93+
}
94+
}
95+
96+
func (certmanager *CertManager) DeleteName(ctx context.Context, id uint64) (*schema.Name, error) {
97+
var name schema.Name
98+
if err := certmanager.conn.Delete(ctx, &name, schema.NameId(id)); err != nil {
99+
return nil, err
100+
} else {
101+
return &name, nil
102+
}
103+
}

pkg/cert/certmanager_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cert_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
// Packages
9+
test "github.com/djthorpe/go-pg/pkg/test"
10+
cert "github.com/mutablelogic/go-server/pkg/cert"
11+
assert "github.com/stretchr/testify/assert"
12+
)
13+
14+
// Global connection variable
15+
var conn test.Conn
16+
17+
// Start up a container and test the pool
18+
func TestMain(m *testing.M) {
19+
test.Main(m, &conn)
20+
}
21+
22+
/////////////////////////////////////////////////////////////////////////////////
23+
// PUBLIC METHODS
24+
25+
func Test_CertManager_001(t *testing.T) {
26+
assert := assert.New(t)
27+
conn := conn.Begin(t)
28+
defer conn.Close()
29+
30+
// Create a new certificate manager with a root certificate
31+
certmanager, err := cert.NewCertManager(context.TODO(), conn,
32+
cert.WithCommonName("root"),
33+
cert.WithRSAKey(4096),
34+
cert.WithExpiry(16*365*24*time.Hour),
35+
cert.WithCA(),
36+
)
37+
if !assert.NoError(err) {
38+
t.FailNow()
39+
}
40+
assert.NotNil(certmanager)
41+
}

pkg/cert/opts.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ func WithAddress(address, postcode string) Opt {
103103
// Set certificate expiry
104104
func WithExpiry(expires time.Duration) Opt {
105105
return func(o *Cert) error {
106-
o.x509.NotBefore = time.Now()
107-
o.x509.NotAfter = o.x509.NotBefore.Add(expires)
106+
o.x509.NotBefore = time.Now().Truncate(time.Second).UTC()
107+
o.x509.NotAfter = o.x509.NotBefore.Add(expires).Truncate(time.Second).UTC()
108108
return nil
109109
}
110110
}

pkg/cert/schema/cert.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package schema
22

33
import (
4+
"encoding/json"
45
"net"
56
"time"
67
)
@@ -22,3 +23,14 @@ type CertMeta struct {
2223
KeyType string `json:"key_type,omitempty"`
2324
KeyBits string `json:"key_subtype,omitempty"`
2425
}
26+
27+
///////////////////////////////////////////////////////////////////////////////
28+
// STRINGIFY
29+
30+
func (c CertMeta) String() string {
31+
data, err := json.MarshalIndent(c, "", " ")
32+
if err != nil {
33+
return err.Error()
34+
}
35+
return string(data)
36+
}

0 commit comments

Comments
 (0)