Skip to content

Commit 5d5c550

Browse files
committed
[csr] add certificate generation
1 parent 088b8a0 commit 5d5c550

File tree

10 files changed

+110
-19
lines changed

10 files changed

+110
-19
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ HTTP__PROXY_HEADER=X-Forwarded-For # HTTP proxy header
33
HTTP__PROXIES=127.0.0.1 # HTTP trusted proxies
44

55
STORAGE__URL=redis://localhost:6379/0 # Redis URL
6+
7+
CSR__CA_CERT_PATH=./ca/ca.crt # CA certificate
8+
CSR__CA_KEY_PATH=./ca/ca.key # CA private key

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ $RECYCLE.BIN/
126126
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
127127

128128
tmp/
129+
ca/
130+
129131
coverage.html
130132

131133
go.work.sum

internal/config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ type StorageConfig struct {
2121
}
2222

2323
type CSR struct {
24-
TTL time.Duration `envconfig:"CSR__TTL"`
24+
TTL time.Duration `envconfig:"CSR__TTL"`
25+
CACertPath string `envconfig:"CSR__CA_CERT_PATH" required:"true"`
26+
CAKeyPath string `envconfig:"CSR__CA_KEY_PATH" required:"true"`
2527
}
2628

2729
type Config struct {

internal/config/module.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package config
22

33
import (
4+
"os"
5+
46
"github.com/android-sms-gateway/ca/internal/api"
57
"github.com/android-sms-gateway/ca/internal/csr"
68
"github.com/android-sms-gateway/ca/pkg/core/http"
@@ -29,8 +31,20 @@ var Module = fx.Module(
2931
}
3032
}),
3133
fx.Provide(func(c Config) csr.Config {
34+
caCert, err := os.ReadFile(c.CSR.CACertPath)
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
caKey, err := os.ReadFile(c.CSR.CAKeyPath)
40+
if err != nil {
41+
panic(err)
42+
}
43+
3244
return csr.Config{
33-
TTL: c.CSR.TTL,
45+
CACert: caCert,
46+
CAKey: caKey,
47+
TTL: c.CSR.TTL,
3448
}
3549
}),
3650
)

internal/csr/ca.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package csr
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/pem"
6+
"fmt"
7+
)
8+
9+
func LoadCA(certPEM, keyPEM []byte) (*x509.Certificate, any, error) {
10+
// Load CA certificate
11+
block, _ := pem.Decode(certPEM)
12+
if block == nil || block.Type != "CERTIFICATE" {
13+
return nil, nil, fmt.Errorf("invalid CA certificate")
14+
}
15+
cert, err := x509.ParseCertificate(block.Bytes)
16+
if err != nil {
17+
return nil, nil, fmt.Errorf("failed to parse CA certificate: %w", err)
18+
}
19+
20+
// Load CA private key
21+
block, _ = pem.Decode(keyPEM)
22+
if block == nil || (block.Type != "RSA PRIVATE KEY" && block.Type != "EC PRIVATE KEY" && block.Type != "PRIVATE KEY") {
23+
return nil, nil, fmt.Errorf("invalid CA private key")
24+
}
25+
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
26+
if err != nil {
27+
return nil, nil, fmt.Errorf("failed to parse CA private key: %w", err)
28+
}
29+
30+
return cert, priv, nil
31+
}

internal/csr/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ package csr
33
import "time"
44

55
type Config struct {
6-
TTL time.Duration
6+
CACert []byte
7+
CAKey []byte
8+
TTL time.Duration
79
}

internal/csr/domain.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@ func (c CSR) toMap() map[string]string {
4040
}
4141

4242
type CSRStatus struct {
43+
CSR
4344
id string
4445
status client.CSRStatus
4546
certificate string
4647
reason string
4748
}
4849

49-
func NewCSRStatus(id string, status client.CSRStatus, certificate string, reason string) CSRStatus {
50+
func NewCSRStatus(id string, content string, metadata map[string]string, status client.CSRStatus, certificate string, reason string) CSRStatus {
5051
return CSRStatus{
52+
CSR: CSR{
53+
content: content,
54+
metadata: metadata,
55+
},
5156
id: id,
5257
status: status,
5358
certificate: certificate,

internal/csr/module.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ var Module = fx.Module(
1414
fx.Provide(func(config Config, redis *redis.Client) *repository {
1515
return newRepository(redis, config.TTL)
1616
}, fx.Private),
17-
fx.Provide(NewService),
17+
fx.Provide(func(config Config, csrs *repository, log *zap.Logger) (*Service, error) {
18+
caCert, caKey, err := LoadCA(config.CACert, config.CAKey)
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
return NewService(csrs, caCert, caKey, log), nil
24+
}),
1825
fx.Invoke(func(lc fx.Lifecycle, s *Service) {
1926
lc.Append(fx.Hook{
2027
OnStop: s.Stop,

internal/csr/repository.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package csr
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
78
"time"
@@ -66,7 +67,13 @@ func (r *repository) Get(ctx context.Context, requestId string) (CSRStatus, erro
6667
return CSRStatus{}, ErrCSRNotFound
6768
}
6869

69-
return NewCSRStatus(requestId, client.CSRStatus(status), res["certificate"], res["reason"]), nil
70+
metadata := map[string]string{}
71+
72+
if err := json.Unmarshal([]byte(res["metadata"]), &metadata); err != nil {
73+
return CSRStatus{}, fmt.Errorf("failed to get csr: %w", err)
74+
}
75+
76+
return NewCSRStatus(requestId, res["content"], metadata, client.CSRStatus(status), res["certificate"], res["reason"]), nil
7077
}
7178

7279
func newRepository(redis *redis.Client, ttl time.Duration) *repository {

internal/csr/service.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package csr
22

33
import (
44
"context"
5+
"crypto/rand"
56
"crypto/x509"
67
"encoding/pem"
78
"errors"
89
"fmt"
910
"math/big"
1011
"runtime"
12+
"strings"
1113
"time"
1214

1315
"github.com/android-sms-gateway/ca/pkg/client"
@@ -20,6 +22,9 @@ import (
2022
type Service struct {
2123
csrs *repository
2224

25+
caCert *x509.Certificate
26+
caKey any
27+
2328
queue *queue.Queue
2429
newid func() string
2530
log *zap.Logger
@@ -57,7 +62,7 @@ func (s *Service) Create(ctx context.Context, csr CSR) (CSRStatus, error) {
5762
s.log.Error("failed to queue csr", zap.Error(err))
5863
}
5964

60-
return NewCSRStatus(id, client.CSRStatusPending, "", ""), nil
65+
return NewCSRStatus(id, csr.content, csr.metadata, client.CSRStatusPending, "", ""), nil
6166
}
6267

6368
func (s *Service) Get(ctx context.Context, id string) (CSRStatus, error) {
@@ -82,13 +87,13 @@ func (s *Service) process(ctx context.Context, m core.TaskMessage) error {
8287
return nil
8388
}
8489

85-
csr, err := s.parseCsr(res.Certificate())
90+
csr, err := s.parseCsr(res.Content())
8691
if err != nil {
8792
return err
8893
}
8994

9095
// Create a signed certificate
91-
_ = &x509.Certificate{
96+
template := &x509.Certificate{
9297
SerialNumber: big.NewInt(1),
9398
Subject: csr.Subject,
9499
NotBefore: time.Now(),
@@ -102,20 +107,22 @@ func (s *Service) process(ctx context.Context, m core.TaskMessage) error {
102107
IPAddresses: csr.IPAddresses,
103108
}
104109

105-
// certBytes, err := x509.CreateCertificate(rand.Reader, template, caKey, csr.PublicKey, caPriv)
106-
// if err != nil {
107-
// return "", fmt.Errorf("failed to sign certificate: %w", err)
108-
// }
110+
certBytes, err := x509.CreateCertificate(rand.Reader, template, s.caCert, csr.PublicKey, s.caKey)
111+
if err != nil {
112+
return fmt.Errorf("failed to sign certificate: %w", err)
113+
}
114+
115+
// Encode the signed certificate to PEM format
116+
var certPEM strings.Builder
117+
if err := pem.Encode(&certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
118+
return fmt.Errorf("failed to encode certificate: %w", err)
119+
}
109120

110-
time.Sleep(time.Second * 10)
121+
s.log.Info("signed certificate", zap.String("id", id), zap.String("csr", res.Certificate()), zap.String("cert", certPEM.String()))
111122

112123
return nil
113124
}
114125

115-
// func LoadCA(certPath, keyPath string) (*x509.Certificate, *rsa.PrivateKey, error) {
116-
117-
// }
118-
119126
func (s *Service) parseCsr(content string) (*x509.CertificateRequest, error) {
120127
block, _ := pem.Decode([]byte(content))
121128
if block == nil || block.Type != "CERTIFICATE REQUEST" {
@@ -125,11 +132,19 @@ func (s *Service) parseCsr(content string) (*x509.CertificateRequest, error) {
125132
return x509.ParseCertificateRequest(block.Bytes)
126133
}
127134

128-
func NewService(csrs *repository, log *zap.Logger) *Service {
135+
func NewService(csrs *repository, caCert *x509.Certificate, caKey any, log *zap.Logger) *Service {
129136
if csrs == nil {
130137
panic("csrs is required")
131138
}
132139

140+
if caCert == nil {
141+
panic("caCert is required")
142+
}
143+
144+
if caKey == nil {
145+
panic("caKey is required")
146+
}
147+
133148
if log == nil {
134149
panic("log is required")
135150
}
@@ -139,6 +154,9 @@ func NewService(csrs *repository, log *zap.Logger) *Service {
139154
s := &Service{
140155
csrs: csrs,
141156

157+
caCert: caCert,
158+
caKey: caKey,
159+
142160
newid: newid,
143161
log: log,
144162
}

0 commit comments

Comments
 (0)