File
Blob: pki/client_rpc_test.go
| 1 | package pki |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | "crypto/rand" |
| 7 | "crypto/tls" |
| 8 | "crypto/x509" |
| 9 | "crypto/x509/pkix" |
| 10 | "math/big" |
| 11 | "testing" |
| 12 | "time" |
| 13 | |
| 14 | "go.miragespace.co/specter/spec/pki" |
| 15 | |
| 16 | "github.com/stretchr/testify/require" |
| 17 | "go.uber.org/zap/zaptest" |
| 18 | ) |
| 19 | |
| 20 | func generateCA(as *require.Assertions) tls.Certificate { |
| 21 | ca := &x509.Certificate{ |
| 22 | SerialNumber: big.NewInt(1234), |
| 23 | Subject: pkix.Name{ |
| 24 | CommonName: "test ca", |
| 25 | }, |
| 26 | NotBefore: time.Now(), |
| 27 | NotAfter: time.Now().AddDate(10, 0, 0), |
| 28 | IsCA: true, |
| 29 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, |
| 30 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, |
| 31 | BasicConstraintsValid: true, |
| 32 | } |
| 33 | |
| 34 | caPubKey, caPrivKey, err := ed25519.GenerateKey(rand.Reader) |
| 35 | as.NoError(err) |
| 36 | |
| 37 | caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, caPubKey, caPrivKey) |
| 38 | as.NoError(err) |
| 39 | |
| 40 | return tls.Certificate{ |
| 41 | Certificate: [][]byte{caBytes}, |
| 42 | PrivateKey: caPrivKey, |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | func TestSigning(t *testing.T) { |
| 47 | as := require.New(t) |
| 48 | logger := zaptest.NewLogger(t) |
| 49 | |
| 50 | cert := generateCA(as) |
| 51 | |
| 52 | server := &Server{ |
| 53 | Logger: logger, |
| 54 | ClientCA: cert, |
| 55 | } |
| 56 | |
| 57 | clientPub, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 58 | as.NoError(err) |
| 59 | req, err := CreateRequest(clientPriv) |
| 60 | as.NoError(err) |
| 61 | |
| 62 | resp, err := server.RequestCertificate(context.Background(), req) |
| 63 | as.NoError(err) |
| 64 | as.NotEmpty(resp.GetCertDer()) |
| 65 | as.NotEmpty(resp.GetCertPem()) |
| 66 | as.EqualValues(pki.MarshalCertificate(resp.GetCertDer()), resp.GetCertPem()) |
| 67 | |
| 68 | ca, err := x509.ParseCertificate(cert.Certificate[0]) |
| 69 | as.NoError(err) |
| 70 | caPool := x509.NewCertPool() |
| 71 | caPool.AddCert(ca) |
| 72 | verify := x509.VerifyOptions{ |
| 73 | Roots: caPool, |
| 74 | KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| 75 | } |
| 76 | |
| 77 | clientCert, err := x509.ParseCertificate(resp.GetCertDer()) |
| 78 | as.NoError(err) |
| 79 | |
| 80 | chains, err := clientCert.Verify(verify) |
| 81 | as.NoError(err) |
| 82 | c := chains[0][0] |
| 83 | as.EqualValues(clientPub, c.PublicKey) |
| 84 | } |
| 85 | |
| 86 | func TestRenewCertificate_Success(t *testing.T) { |
| 87 | as := require.New(t) |
| 88 | logger := zaptest.NewLogger(t) |
| 89 | |
| 90 | ca := generateCA(as) |
| 91 | |
| 92 | server := &Server{ |
| 93 | Logger: logger, |
| 94 | ClientCA: ca, |
| 95 | } |
| 96 | |
| 97 | // Generate initial certificate |
| 98 | clientPub, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 99 | as.NoError(err) |
| 100 | req, err := CreateRequest(clientPriv) |
| 101 | as.NoError(err) |
| 102 | |
| 103 | resp, err := server.RequestCertificate(context.Background(), req) |
| 104 | as.NoError(err) |
| 105 | as.NotEmpty(resp.GetCertDer()) |
| 106 | |
| 107 | oldCert, err := x509.ParseCertificate(resp.GetCertDer()) |
| 108 | as.NoError(err) |
| 109 | oldCN := oldCert.Subject.CommonName |
| 110 | |
| 111 | // Generate renewal request with same key |
| 112 | renewReq, err := CreateRenewalRequest(clientPriv, resp.GetCertDer()) |
| 113 | as.NoError(err) |
| 114 | |
| 115 | renewResp, err := server.RenewCertificate(context.Background(), renewReq) |
| 116 | as.NoError(err) |
| 117 | as.NotEmpty(renewResp.GetCertDer()) |
| 118 | as.NotEmpty(renewResp.GetCertPem()) |
| 119 | as.EqualValues(pki.MarshalCertificate(renewResp.GetCertDer()), renewResp.GetCertPem()) |
| 120 | |
| 121 | // Verify renewed cert |
| 122 | caCert, err := x509.ParseCertificate(ca.Certificate[0]) |
| 123 | as.NoError(err) |
| 124 | caPool := x509.NewCertPool() |
| 125 | caPool.AddCert(caCert) |
| 126 | verify := x509.VerifyOptions{ |
| 127 | Roots: caPool, |
| 128 | KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| 129 | } |
| 130 | |
| 131 | newCert, err := x509.ParseCertificate(renewResp.GetCertDer()) |
| 132 | as.NoError(err) |
| 133 | |
| 134 | chains, err := newCert.Verify(verify) |
| 135 | as.NoError(err) |
| 136 | as.Len(chains, 1) |
| 137 | |
| 138 | // Verify CN is preserved |
| 139 | as.Equal(oldCN, newCert.Subject.CommonName) |
| 140 | // Verify public key is the same |
| 141 | as.EqualValues(clientPub, newCert.PublicKey) |
| 142 | } |
| 143 | |
| 144 | func TestRenewCertificate_InvalidCA(t *testing.T) { |
| 145 | as := require.New(t) |
| 146 | logger := zaptest.NewLogger(t) |
| 147 | |
| 148 | ca := generateCA(as) |
| 149 | differentCA := generateCA(as) // Different CA |
| 150 | |
| 151 | server := &Server{ |
| 152 | Logger: logger, |
| 153 | ClientCA: ca, |
| 154 | } |
| 155 | |
| 156 | // Generate certificate with different CA |
| 157 | clientPub, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 158 | as.NoError(err) |
| 159 | |
| 160 | // Create cert signed by different CA |
| 161 | certBytes, err := pki.GenerateCertificate(logger, differentCA, pki.IdentityRequest{ |
| 162 | PublicKey: clientPub, |
| 163 | Subject: pki.MakeSubjectV2(12345, []byte("test")), |
| 164 | }) |
| 165 | as.NoError(err) |
| 166 | |
| 167 | renewReq, err := CreateRenewalRequest(clientPriv, certBytes) |
| 168 | as.NoError(err) |
| 169 | |
| 170 | _, err = server.RenewCertificate(context.Background(), renewReq) |
| 171 | as.Error(err) |
| 172 | as.Contains(err.Error(), "permission_denied") |
| 173 | } |
| 174 | |
| 175 | func TestRenewCertificate_BadProof(t *testing.T) { |
| 176 | as := require.New(t) |
| 177 | logger := zaptest.NewLogger(t) |
| 178 | |
| 179 | ca := generateCA(as) |
| 180 | |
| 181 | server := &Server{ |
| 182 | Logger: logger, |
| 183 | ClientCA: ca, |
| 184 | } |
| 185 | |
| 186 | // Generate initial certificate |
| 187 | _, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 188 | as.NoError(err) |
| 189 | req, err := CreateRequest(clientPriv) |
| 190 | as.NoError(err) |
| 191 | |
| 192 | resp, err := server.RequestCertificate(context.Background(), req) |
| 193 | as.NoError(err) |
| 194 | |
| 195 | // Generate PoW with DIFFERENT key |
| 196 | _, differentPriv, err := ed25519.GenerateKey(rand.Reader) |
| 197 | as.NoError(err) |
| 198 | renewReq, err := CreateRenewalRequest(differentPriv, resp.GetCertDer()) |
| 199 | as.NoError(err) |
| 200 | |
| 201 | _, err = server.RenewCertificate(context.Background(), renewReq) |
| 202 | as.Error(err) |
| 203 | as.Contains(err.Error(), "permission_denied") |
| 204 | } |
| 205 | |
| 206 | func TestRenewCertificate_V1Unsupported(t *testing.T) { |
| 207 | as := require.New(t) |
| 208 | logger := zaptest.NewLogger(t) |
| 209 | |
| 210 | ca := generateCA(as) |
| 211 | |
| 212 | server := &Server{ |
| 213 | Logger: logger, |
| 214 | ClientCA: ca, |
| 215 | } |
| 216 | |
| 217 | // Generate v1 certificate |
| 218 | clientPub, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 219 | as.NoError(err) |
| 220 | |
| 221 | v1Subject := pki.MakeSubjectV1(12345, "oldtoken") |
| 222 | certBytes, err := pki.GenerateCertificate(logger, ca, pki.IdentityRequest{ |
| 223 | PublicKey: clientPub, |
| 224 | Subject: v1Subject, |
| 225 | }) |
| 226 | as.NoError(err) |
| 227 | |
| 228 | renewReq, err := CreateRenewalRequest(clientPriv, certBytes) |
| 229 | as.NoError(err) |
| 230 | |
| 231 | _, err = server.RenewCertificate(context.Background(), renewReq) |
| 232 | as.Error(err) |
| 233 | as.Contains(err.Error(), "failed_precondition") |
| 234 | as.Contains(err.Error(), "v1 certificates cannot be renewed") |
| 235 | } |
| 236 | |
| 237 | func TestRenewCertificate_MissingCertDer(t *testing.T) { |
| 238 | as := require.New(t) |
| 239 | logger := zaptest.NewLogger(t) |
| 240 | |
| 241 | ca := generateCA(as) |
| 242 | |
| 243 | server := &Server{ |
| 244 | Logger: logger, |
| 245 | ClientCA: ca, |
| 246 | } |
| 247 | |
| 248 | _, clientPriv, err := ed25519.GenerateKey(rand.Reader) |
| 249 | as.NoError(err) |
| 250 | |
| 251 | renewReq, err := CreateRenewalRequest(clientPriv, nil) |
| 252 | as.NoError(err) |
| 253 | |
| 254 | _, err = server.RenewCertificate(context.Background(), renewReq) |
| 255 | as.Error(err) |
| 256 | as.Contains(err.Error(), "current_cert_der") |
| 257 | } |