Skip to content
File

Blob: pki/client_rpc.go

go146 lines
1package pki
2 
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "crypto/sha256"
8 "crypto/tls"
9 "crypto/x509"
10 "encoding/base64"
11 
12 "go.miragespace.co/specter/spec/chord"
13 "go.miragespace.co/specter/spec/pki"
14 "go.miragespace.co/specter/spec/pow"
15 "go.miragespace.co/specter/spec/protocol"
16 
17 "github.com/twitchtv/twirp"
18 "go.uber.org/zap"
19)
20 
21type Server struct {
22 Logger *zap.Logger
23 ClientCA tls.Certificate
24}
25 
26var _ protocol.PKIService = (*Server)(nil)
27 
28func (p *Server) RequestCertificate(ctx context.Context, req *protocol.CertificateRequest) (*protocol.CertificateResponse, error) {
29 var hashed []byte
30 
31 d, err := pow.VerifySolution(req.GetProof(), pow.Parameters{
32 Difficulty: pki.HashcashDifficulty,
33 Expires: pki.HashcashExpires,
34 GetSubject: func(pubKey ed25519.PublicKey) string {
35 h := sha256.New()
36 h.Write(pubKey)
37 hashed = h.Sum(nil)
38 return base64.URLEncoding.EncodeToString(hashed)
39 },
40 })
41 if err != nil {
42 return nil, err
43 }
44 
45 id := chord.Random()
46 certSubject := pki.MakeSubjectV2(id, hashed)
47 
48 certBytes, err := pki.GenerateCertificate(p.Logger, p.ClientCA, pki.IdentityRequest{
49 PublicKey: d.PubKey,
50 Subject: certSubject,
51 })
52 if err != nil {
53 return nil, twirp.InternalErrorWith(err)
54 }
55 
56 certPem := pki.MarshalCertificate(certBytes)
57 
58 return &protocol.CertificateResponse{
59 CertDer: certBytes,
60 CertPem: certPem,
61 }, nil
62}
63 
64func (p *Server) RenewCertificate(ctx context.Context, req *protocol.CertificateRenewalRequest) (*protocol.CertificateResponse, error) {
65 // Validate current_cert_der is provided
66 if len(req.GetCurrentCertDer()) == 0 {
67 return nil, twirp.RequiredArgumentError("current_cert_der")
68 }
69 
70 // Parse the current certificate
71 oldCert, err := x509.ParseCertificate(req.GetCurrentCertDer())
72 if err != nil {
73 return nil, twirp.InvalidArgumentError("current_cert_der", "failed to parse certificate")
74 }
75 
76 // Verify certificate is from our Client CA
77 caCert, err := x509.ParseCertificate(p.ClientCA.Certificate[0])
78 if err != nil {
79 return nil, twirp.InternalErrorWith(err)
80 }
81 caPool := x509.NewCertPool()
82 caPool.AddCert(caCert)
83 
84 _, err = oldCert.Verify(x509.VerifyOptions{
85 Roots: caPool,
86 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
87 })
88 if err != nil {
89 return nil, twirp.PermissionDenied.Error("certificate not issued by this CA")
90 }
91 
92 // Extract identity and check version
93 identity, err := pki.ExtractCertificateIdentity(oldCert)
94 if err != nil {
95 return nil, twirp.InvalidArgumentError("current_cert_der", "failed to extract identity from certificate")
96 }
97 
98 // Reject v1 certificates - they must use manual migration
99 if identity.Version == pki.TokenV1 {
100 return nil, twirp.FailedPrecondition.Error("v1 certificates cannot be renewed; please use the migration tool (util/migrator) to upgrade to v2")
101 }
102 
103 // Verify PoW with same parameters as initial issuance
104 var hashed []byte
105 d, err := pow.VerifySolution(req.GetProof(), pow.Parameters{
106 Difficulty: pki.HashcashDifficulty,
107 Expires: pki.HashcashExpires,
108 GetSubject: func(pubKey ed25519.PublicKey) string {
109 h := sha256.New()
110 h.Write(pubKey)
111 hashed = h.Sum(nil)
112 return base64.URLEncoding.EncodeToString(hashed)
113 },
114 })
115 if err != nil {
116 return nil, err
117 }
118 
119 // Verify PoW public key matches the certificate's public key
120 oldPubKey, ok := oldCert.PublicKey.(ed25519.PublicKey)
121 if !ok {
122 return nil, twirp.InvalidArgumentError("current_cert_der", "certificate does not contain an ed25519 public key")
123 }
124 if !bytes.Equal(d.PubKey, oldPubKey) {
125 return nil, twirp.PermissionDenied.Error("proof does not match current certificate key")
126 }
127 
128 // Issue renewed certificate with the same subject (preserves CN/identity)
129 certBytes, err := pki.GenerateCertificate(p.Logger, p.ClientCA, pki.IdentityRequest{
130 PublicKey: d.PubKey,
131 Subject: oldCert.Subject, // Preserve exact subject from old certificate
132 })
133 if err != nil {
134 return nil, twirp.InternalErrorWith(err)
135 }
136 
137 certPem := pki.MarshalCertificate(certBytes)
138 
139 p.Logger.Info("Certificate renewed", zap.Object("identity", identity))
140 
141 return &protocol.CertificateResponse{
142 CertDer: certBytes,
143 CertPem: certPem,
144 }, nil
145}