diff --git a/cmd/admin/cert.go b/cmd/admin/cert.go index 0fb2e06906f..b0413d2cbed 100644 --- a/cmd/admin/cert.go +++ b/cmd/admin/cert.go @@ -44,6 +44,7 @@ type subcommandRevokeCert struct { privKey string regID uint certFile string + crlShard int64 } var _ subcommand = (*subcommandRevokeCert)(nil) @@ -58,6 +59,7 @@ func (s *subcommandRevokeCert) Flags(flag *flag.FlagSet) { flag.StringVar(&s.reasonStr, "reason", "unspecified", "Revocation reason (unspecified, keyCompromise, superseded, cessationOfOperation, or privilegeWithdrawn)") flag.BoolVar(&s.skipBlock, "skip-block-key", false, "Skip blocking the key, if revoked for keyCompromise - use with extreme caution") flag.BoolVar(&s.malformed, "malformed", false, "Indicates that the cert cannot be parsed - use with caution") + flag.Int64Var(&s.crlShard, "crl-shard", 0, "For malformed certs, the CRL shard the certificate belongs to") // Flags specifying the input method for the certificates to be revoked. flag.StringVar(&s.serial, "serial", "", "Revoke the certificate with this hex serial") @@ -134,12 +136,22 @@ func (s *subcommandRevokeCert) Run(ctx context.Context, a *admin) error { return fmt.Errorf("collecting serials to revoke: %w", err) } + serials, err = cleanSerials(serials) + if err != nil { + return err + } + if len(serials) == 0 { return errors.New("no serials to revoke found") } + a.log.Infof("Found %d certificates to revoke", len(serials)) - err = a.revokeSerials(ctx, serials, reasonCode, s.malformed, s.skipBlock, s.parallelism) + if s.malformed { + return s.revokeMalformed(ctx, a, serials, reasonCode) + } + + err = a.revokeSerials(ctx, serials, reasonCode, s.skipBlock, s.parallelism) if err != nil { return fmt.Errorf("revoking serials: %w", err) } @@ -147,6 +159,31 @@ func (s *subcommandRevokeCert) Run(ctx context.Context, a *admin) error { return nil } +func (s *subcommandRevokeCert) revokeMalformed(ctx context.Context, a *admin, serials []string, reasonCode revocation.Reason) error { + u, err := user.Current() + if err != nil { + return fmt.Errorf("getting admin username: %w", err) + } + if s.crlShard == 0 { + return errors.New("when revoking malformed certificates, a nonzero CRL shard must be specified") + } + if len(serials) > 1 { + return errors.New("when revoking malformed certificates, only one cert at a time is allowed") + } + _, err = a.rac.AdministrativelyRevokeCertificate( + ctx, + &rapb.AdministrativelyRevokeCertificateRequest{ + Serial: serials[0], + Code: int64(reasonCode), + AdminName: u.Username, + SkipBlockKey: s.skipBlock, + Malformed: true, + CrlShard: s.crlShard, + }, + ) + return err +} + func (a *admin) serialsFromIncidentTable(ctx context.Context, tableName string) ([]string, error) { stream, err := a.saroc.SerialsForIncident(ctx, &sapb.SerialsForIncidentRequest{IncidentTable: tableName}) if err != nil { @@ -248,7 +285,9 @@ func (a *admin) serialsFromCertPEM(_ context.Context, filename string) ([]string return []string{core.SerialToString(cert.SerialNumber)}, nil } -func cleanSerial(serial string) (string, error) { +// cleanSerials removes non-alphanumeric characters from the serials and checks +// that all resulting serials are valid (hex encoded, and the correct length). +func cleanSerials(serials []string) ([]string, error) { serialStrip := func(r rune) rune { switch { case unicode.IsLetter(r): @@ -258,14 +297,19 @@ func cleanSerial(serial string) (string, error) { } return rune(-1) } - strippedSerial := strings.Map(serialStrip, serial) - if !core.ValidSerial(strippedSerial) { - return "", fmt.Errorf("cleaned serial %q is not valid", strippedSerial) + + var ret []string + for _, s := range serials { + cleaned := strings.Map(serialStrip, s) + if !core.ValidSerial(cleaned) { + return nil, fmt.Errorf("cleaned serial %q is not valid", cleaned) + } + ret = append(ret, cleaned) } - return strippedSerial, nil + return ret, nil } -func (a *admin) revokeSerials(ctx context.Context, serials []string, reason revocation.Reason, malformed bool, skipBlockKey bool, parallelism uint) error { +func (a *admin) revokeSerials(ctx context.Context, serials []string, reason revocation.Reason, skipBlockKey bool, parallelism uint) error { u, err := user.Current() if err != nil { return fmt.Errorf("getting admin username: %w", err) @@ -279,19 +323,17 @@ func (a *admin) revokeSerials(ctx context.Context, serials []string, reason revo go func() { defer wg.Done() for serial := range work { - cleanedSerial, err := cleanSerial(serial) - if err != nil { - a.log.Errf("skipping serial %q: %s", serial, err) - continue - } _, err = a.rac.AdministrativelyRevokeCertificate( ctx, &rapb.AdministrativelyRevokeCertificateRequest{ - Serial: cleanedSerial, + Serial: serial, Code: int64(reason), AdminName: u.Username, SkipBlockKey: skipBlockKey, - Malformed: malformed, + // This is a well-formed certificate so send CrlShard 0 + // to let the RA figure out the right shard from the cert. + Malformed: false, + CrlShard: 0, }, ) if err != nil { diff --git a/cmd/admin/cert_test.go b/cmd/admin/cert_test.go index 185d497010b..788348de85b 100644 --- a/cmd/admin/cert_test.go +++ b/cmd/admin/cert_test.go @@ -10,6 +10,7 @@ import ( "errors" "os" "path" + "reflect" "slices" "strings" "sync" @@ -198,20 +199,20 @@ func (mra *mockRARecordingRevocations) reset() { func TestRevokeSerials(t *testing.T) { t.Parallel() serials := []string{ - "2a:18:59:2b:7f:4b:f5:96:fb:1a:1d:f1:35:56:7a:cd:82:5a", - "03:8c:3f:63:88:af:b7:69:5d:d4:d6:bb:e3:d2:64:f1:e4:e2", - "048c3f6388afb7695dd4d6bbe3d264f1e5e5!", + "2a18592b7f4bf596fb1a1df135567acd825a", + "038c3f6388afb7695dd4d6bbe3d264f1e4e2", + "048c3f6388afb7695dd4d6bbe3d264f1e5e5", } mra := mockRARecordingRevocations{} log := blog.NewMock() a := admin{rac: &mra, log: log} - assertRequestsContain := func(reqs []*rapb.AdministrativelyRevokeCertificateRequest, code revocation.Reason, skipBlockKey bool, malformed bool) { + assertRequestsContain := func(reqs []*rapb.AdministrativelyRevokeCertificateRequest, code revocation.Reason, skipBlockKey bool) { + t.Helper() for _, req := range reqs { test.AssertEquals(t, len(req.Cert), 0) test.AssertEquals(t, req.Code, int64(code)) test.AssertEquals(t, req.SkipBlockKey, skipBlockKey) - test.AssertEquals(t, req.Malformed, malformed) } } @@ -219,49 +220,113 @@ func TestRevokeSerials(t *testing.T) { mra.reset() log.Clear() a.dryRun = false - err := a.revokeSerials(context.Background(), serials, 0, false, false, 1) + err := a.revokeSerials(context.Background(), serials, 0, false, 1) test.AssertEquals(t, len(log.GetAllMatching("invalid serial format")), 0) test.AssertNotError(t, err, "") test.AssertEquals(t, len(log.GetAll()), 0) test.AssertEquals(t, len(mra.revocationRequests), 3) - assertRequestsContain(mra.revocationRequests, 0, false, false) + assertRequestsContain(mra.revocationRequests, 0, false) // Revoking an already-revoked serial should result in one log line. mra.reset() log.Clear() mra.alreadyRevoked = []string{"048c3f6388afb7695dd4d6bbe3d264f1e5e5"} - err = a.revokeSerials(context.Background(), serials, 0, false, false, 1) + err = a.revokeSerials(context.Background(), serials, 0, false, 1) + t.Logf("error: %s", err) + t.Logf("logs: %s", strings.Join(log.GetAll(), "")) test.AssertError(t, err, "already-revoked should result in error") test.AssertEquals(t, len(log.GetAllMatching("not revoking")), 1) test.AssertEquals(t, len(mra.revocationRequests), 3) - assertRequestsContain(mra.revocationRequests, 0, false, false) + assertRequestsContain(mra.revocationRequests, 0, false) // Revoking a doomed-to-fail serial should also result in one log line. mra.reset() log.Clear() mra.doomedToFail = []string{"048c3f6388afb7695dd4d6bbe3d264f1e5e5"} - err = a.revokeSerials(context.Background(), serials, 0, false, false, 1) + err = a.revokeSerials(context.Background(), serials, 0, false, 1) test.AssertError(t, err, "gRPC error should result in error") test.AssertEquals(t, len(log.GetAllMatching("failed to revoke")), 1) test.AssertEquals(t, len(mra.revocationRequests), 3) - assertRequestsContain(mra.revocationRequests, 0, false, false) + assertRequestsContain(mra.revocationRequests, 0, false) // Revoking with other parameters should get carried through. mra.reset() log.Clear() - err = a.revokeSerials(context.Background(), serials, 1, true, true, 3) + err = a.revokeSerials(context.Background(), serials, 1, true, 3) test.AssertNotError(t, err, "") test.AssertEquals(t, len(mra.revocationRequests), 3) - assertRequestsContain(mra.revocationRequests, 1, true, true) + assertRequestsContain(mra.revocationRequests, 1, true) // Revoking in dry-run mode should result in no gRPC requests and three logs. mra.reset() log.Clear() a.dryRun = true a.rac = dryRunRAC{log: log} - err = a.revokeSerials(context.Background(), serials, 0, false, false, 1) + err = a.revokeSerials(context.Background(), serials, 0, false, 1) test.AssertNotError(t, err, "") test.AssertEquals(t, len(log.GetAllMatching("dry-run:")), 3) test.AssertEquals(t, len(mra.revocationRequests), 0) - assertRequestsContain(mra.revocationRequests, 0, false, false) + assertRequestsContain(mra.revocationRequests, 0, false) +} + +func TestRevokeMalformed(t *testing.T) { + t.Parallel() + mra := mockRARecordingRevocations{} + log := blog.NewMock() + a := &admin{ + rac: &mra, + log: log, + dryRun: false, + } + + s := subcommandRevokeCert{ + crlShard: 623, + } + serial := "0379c3dfdd518be45948f2dbfa6ea3e9b209" + err := s.revokeMalformed(context.Background(), a, []string{serial}, 1) + if err != nil { + t.Errorf("revokedMalformed with crlShard 623: want success, got %s", err) + } + if len(mra.revocationRequests) != 1 { + t.Errorf("revokeMalformed: want 1 revocation request to SA, got %v", mra.revocationRequests) + } + if mra.revocationRequests[0].Serial != serial { + t.Errorf("revokeMalformed: want %s to be revoked, got %s", serial, mra.revocationRequests[0]) + } + + s = subcommandRevokeCert{ + crlShard: 0, + } + err = s.revokeMalformed(context.Background(), a, []string{"038c3f6388afb7695dd4d6bbe3d264f1e4e2"}, 1) + if err == nil { + t.Errorf("revokedMalformed with crlShard 0: want error, got none") + } + + s = subcommandRevokeCert{ + crlShard: 623, + } + err = s.revokeMalformed(context.Background(), a, []string{"038c3f6388afb7695dd4d6bbe3d264f1e4e2", "28a94f966eae14e525777188512ddf5a0a3b"}, 1) + if err == nil { + t.Errorf("revokedMalformed with multiple serials: want error, got none") + } +} + +func TestCleanSerials(t *testing.T) { + input := []string{ + "2a:18:59:2b:7f:4b:f5:96:fb:1a:1d:f1:35:56:7a:cd:82:5a", + "03:8c:3f:63:88:af:b7:69:5d:d4:d6:bb:e3:d2:64:f1:e4:e2", + "038c3f6388afb7695dd4d6bbe3d264f1e4e2", + } + expected := []string{ + "2a18592b7f4bf596fb1a1df135567acd825a", + "038c3f6388afb7695dd4d6bbe3d264f1e4e2", + "038c3f6388afb7695dd4d6bbe3d264f1e4e2", + } + output, err := cleanSerials(input) + if err != nil { + t.Errorf("cleanSerials(%s): %s, want %s", input, err, expected) + } + if !reflect.DeepEqual(output, expected) { + t.Errorf("cleanSerials(%s)=%s, want %s", input, output, expected) + } } diff --git a/ra/proto/ra.pb.go b/ra/proto/ra.pb.go index bbcc03dc1e5..acac70b1602 100644 --- a/ra/proto/ra.pb.go +++ b/ra/proto/ra.pb.go @@ -424,6 +424,14 @@ type AdministrativelyRevokeCertificateRequest struct { // certificate in question. In this case, the keyCompromise reason cannot be // specified, because the key cannot be blocked. Malformed bool `protobuf:"varint,6,opt,name=malformed,proto3" json:"malformed,omitempty"` + // The CRL shard to store the revocation in. + // + // This is used when revoking malformed certificates, to allow human judgement + // in setting the CRL shard instead of automatically determining it by parsing + // the certificate. + // + // Passing a nonzero crlShard with malformed=false returns error. + CrlShard int64 `protobuf:"varint,7,opt,name=crlShard,proto3" json:"crlShard,omitempty"` } func (x *AdministrativelyRevokeCertificateRequest) Reset() { @@ -500,6 +508,13 @@ func (x *AdministrativelyRevokeCertificateRequest) GetMalformed() bool { return false } +func (x *AdministrativelyRevokeCertificateRequest) GetCrlShard() int64 { + if x != nil { + return x.CrlShard + } + return 0 +} + type NewOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -819,7 +834,7 @@ var file_ra_proto_rawDesc = []byte{ 0x49, 0x44, 0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, - 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xca, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xe6, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, @@ -832,103 +847,105 @@ var file_ra_proto_rawDesc = []byte{ 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, - 0x6d, 0x65, 0x64, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x1a, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, - 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, - 0x05, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, - 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, - 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x22, - 0x3f, 0x0a, 0x15, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, - 0x22, 0x2e, 0x0a, 0x16, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x32, 0x9f, 0x08, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, - 0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, - 0x12, 0x4f, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72, 0x66, - 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x16, 0x44, - 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, - 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, - 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, - 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, - 0x61, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, - 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, - 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, + 0x6d, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x22, + 0xc1, 0x01, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x64, + 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, + 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, + 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, + 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, + 0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, + 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x22, 0x3f, 0x0a, 0x15, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, - 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, - 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x2e, 0x0a, 0x16, + 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x9f, 0x08, 0x0a, + 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, + 0x12, 0x24, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x15, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, + 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, + 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, + 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, + 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, + 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, + 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, + 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, + 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, + 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, + 0x72, 0x61, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x10, 0x2e, 0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, + 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, + 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, + 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, + 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/ra/proto/ra.proto b/ra/proto/ra.proto index c73b4c7cb6a..ef81a19f441 100644 --- a/ra/proto/ra.proto +++ b/ra/proto/ra.proto @@ -73,6 +73,14 @@ message AdministrativelyRevokeCertificateRequest { // certificate in question. In this case, the keyCompromise reason cannot be // specified, because the key cannot be blocked. bool malformed = 6; + // The CRL shard to store the revocation in. + // + // This is used when revoking malformed certificates, to allow human judgement + // in setting the CRL shard instead of automatically determining it by parsing + // the certificate. + // + // Passing a nonzero crlShard with malformed=false returns error. + int64 crlShard = 7; } message NewOrderRequest { diff --git a/ra/ra.go b/ra/ra.go index 65cabc73794..6e2a978c6e9 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "net" "net/url" "slices" @@ -344,6 +343,7 @@ type certificateRevocationEvent struct { // RequesterID is the account ID of the requester. // Will be zero for admin revocations. RequesterID int64 `json:",omitempty"` + CRLShard int64 // AdminName is the name of the admin requester. // Will be zero for subscriber revocations. AdminName string `json:",omitempty"` @@ -1561,14 +1561,20 @@ func (ra *RegistrationAuthorityImpl) PerformValidation( // revokeCertificate updates the database to mark the certificate as revoked, // with the given reason and current timestamp. -func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, serial *big.Int, issuerID issuance.NameID, reason revocation.Reason) error { - serialString := core.SerialToString(serial) +func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, cert *x509.Certificate, reason revocation.Reason) error { + serialString := core.SerialToString(cert.SerialNumber) + issuerID := issuance.IssuerNameID(cert) + shardIdx, err := crlShard(cert) + if err != nil { + return err + } - _, err := ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{ + _, err = ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{ Serial: serialString, Reason: int64(reason), Date: timestamppb.New(ra.clk.Now()), IssuerID: int64(issuerID), + ShardIdx: shardIdx, }) if err != nil { return err @@ -1582,9 +1588,7 @@ func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, seri // as revoked, with the given reason and current timestamp. This only works for // certificates that were previously revoked for a reason other than // keyCompromise, and which are now being updated to keyCompromise instead. -func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx context.Context, serial *big.Int, issuerID issuance.NameID) error { - serialString := core.SerialToString(serial) - +func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx context.Context, serialString string, issuerID issuance.NameID) error { status, err := ra.SA.GetCertificateStatus(ctx, &sapb.Serial{Serial: serialString}) if err != nil { return berrors.NotFoundError("unable to confirm that serial %q was ever issued: %s", serialString, err) @@ -1725,11 +1729,9 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByApplicant(ctx context.Context, logEvent.Reason = req.Code } - issuerID := issuance.IssuerNameID(cert) err = ra.revokeCertificate( ctx, - cert.SerialNumber, - issuerID, + cert, revocation.Reason(req.Code), ) if err != nil { @@ -1737,11 +1739,50 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByApplicant(ctx context.Context, } // Don't propagate purger errors to the client. + issuerID := issuance.IssuerNameID(cert) _ = ra.purgeOCSPCache(ctx, cert, issuerID) return &emptypb.Empty{}, nil } +// crlShard extracts the CRL shard from a certificate's CRLDistributionPoint. +// +// If there is no CRLDistributionPoint, returns 0. +// +// If there is more than one CRLDistributionPoint, returns an error. +// +// Assumes the shard number is represented in the URL as an integer that +// occurs in the last path component, optionally followed by ".crl". +// +// Note: This assumes (a) the CA is generating well-formed, correct +// CRLDistributionPoints and (b) an earlier component has verified the signature +// on this certificate comes from one of our issuers. +func crlShard(cert *x509.Certificate) (int64, error) { + if len(cert.CRLDistributionPoints) == 0 { + return 0, nil + } + if len(cert.CRLDistributionPoints) > 1 { + return 0, errors.New("too many crlDistributionPoints in certificate") + } + + url := strings.TrimSuffix(cert.CRLDistributionPoints[0], ".crl") + lastIndex := strings.LastIndex(url, "/") + if lastIndex == -1 { + return 0, fmt.Errorf("malformed CRLDistributionPoint %q", url) + } + shardStr := url[lastIndex+1:] + shardIdx, err := strconv.Atoi(shardStr) + if err != nil { + return 0, fmt.Errorf("parsing CRLDistributionPoint: %s", err) + } + + if shardIdx <= 0 { + return 0, fmt.Errorf("invalid shard in CRLDistributionPoint: %d", shardIdx) + } + + return int64(shardIdx), nil +} + // addToBlockedKeys initiates a GRPC call to have the Base64-encoded SHA256 // digest of a provided public key added to the blockedKeys table. func (ra *RegistrationAuthorityImpl) addToBlockedKeys(ctx context.Context, key crypto.PublicKey, src string, comment string) error { @@ -1781,8 +1822,6 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r return nil, err } - issuerID := issuance.IssuerNameID(cert) - logEvent := certificateRevocationEvent{ ID: core.NewToken(), SerialNumber: core.SerialToString(cert.SerialNumber), @@ -1807,8 +1846,7 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r // since that addition needs to happen no matter what. revokeErr := ra.revokeCertificate( ctx, - cert.SerialNumber, - issuerID, + cert, revocation.Reason(ocsp.KeyCompromise), ) @@ -1820,6 +1858,8 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r return nil, err } + issuerID := issuance.IssuerNameID(cert) + // Check the error returned from revokeCertificate itself. err = revokeErr if err == nil { @@ -1831,7 +1871,7 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r } else if errors.Is(err, berrors.AlreadyRevoked) { // If it was an AlreadyRevoked error, try to re-revoke the cert in case // it was revoked for a reason other than keyCompromise. - err = ra.updateRevocationForKeyCompromise(ctx, cert.SerialNumber, issuerID) + err = ra.updateRevocationForKeyCompromise(ctx, core.SerialToString(cert.SerialNumber), issuerID) // Perform an Akamai cache purge to handle occurrences of a client // previously successfully revoking a certificate, but the cache purge had @@ -1863,6 +1903,9 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte if req.Serial == "" { return nil, errIncompleteGRPCRequest } + if req.CrlShard != 0 && !req.Malformed { + return nil, errors.New("non-zero CRLShard is only allowed for malformed certificates (shard is automatic for well formed certificates)") + } reasonCode := revocation.Reason(req.Code) if _, present := revocation.AdminAllowedReasons[reasonCode]; !present { @@ -1879,6 +1922,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte ID: core.NewToken(), SerialNumber: req.Serial, Reason: req.Code, + CRLShard: req.CrlShard, Method: "admin", AdminName: req.AdminName, } @@ -1896,6 +1940,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte var cert *x509.Certificate var issuerID issuance.NameID + var shard int64 if req.Cert != nil { // If the incoming request includes a certificate body, just use that and // avoid doing any database queries. This code path is deprecated and will @@ -1905,6 +1950,10 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte return nil, err } issuerID = issuance.IssuerNameID(cert) + shard, err = crlShard(cert) + if err != nil { + return nil, err + } } else if !req.Malformed { // As long as we don't believe the cert will be malformed, we should // get the precertificate so we can block its pubkey if necessary and purge @@ -1922,6 +1971,10 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte return nil, err } issuerID = issuance.IssuerNameID(cert) + shard, err = crlShard(cert) + if err != nil { + return nil, err + } } else { // But if the cert is malformed, we at least still need its IssuerID. var status *corepb.CertificateStatus @@ -1930,29 +1983,30 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte return nil, fmt.Errorf("unable to confirm that serial %q was ever issued: %w", req.Serial, err) } issuerID = issuance.NameID(status.IssuerID) + shard = req.CrlShard } - var serialInt *big.Int - serialInt, err = core.StringToSerial(req.Serial) - if err != nil { - return nil, err - } - - err = ra.revokeCertificate(ctx, serialInt, issuerID, revocation.Reason(req.Code)) + _, err = ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{ + Serial: req.Serial, + Reason: req.Code, + Date: timestamppb.New(ra.clk.Now()), + IssuerID: int64(issuerID), + ShardIdx: shard, + }) // Perform an Akamai cache purge to handle occurrences of a client // successfully revoking a certificate, but the initial cache purge failing. if errors.Is(err, berrors.AlreadyRevoked) { if cert != nil { err = ra.purgeOCSPCache(ctx, cert, issuerID) if err != nil { - err = fmt.Errorf("OCSP cache purge for already revoked serial %v failed: %w", serialInt, err) + err = fmt.Errorf("OCSP cache purge for already revoked serial %v failed: %w", req.Serial, err) return nil, err } } } if err != nil { if req.Code == ocsp.KeyCompromise && errors.Is(err, berrors.AlreadyRevoked) { - err = ra.updateRevocationForKeyCompromise(ctx, serialInt, issuerID) + err = ra.updateRevocationForKeyCompromise(ctx, req.Serial, issuerID) if err != nil { return nil, err } @@ -1973,7 +2027,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte if cert != nil { err = ra.purgeOCSPCache(ctx, cert, issuerID) if err != nil { - err = fmt.Errorf("OCSP cache purge for serial %v failed: %w", serialInt, err) + err = fmt.Errorf("OCSP cache purge for serial %v failed: %w", req.Serial, err) return nil, err } } diff --git a/ra/ra_test.go b/ra/ra_test.go index 98832a05648..a37b3b5e8e1 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -3730,8 +3730,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { }) test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed") test.AssertEquals(t, len(mockSA.blocked), 0) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 1) // Revoking a serial for an unspecified reason should work but not block the key. mockSA.reset() @@ -3742,8 +3740,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { }) test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed") test.AssertEquals(t, len(mockSA.blocked), 0) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2) // Duplicate administrative revocation of a serial for an unspecified reason // should succeed because the akamai cache purge succeeds. @@ -3755,8 +3751,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { }) test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed") test.AssertEquals(t, len(mockSA.blocked), 0) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2) // Duplicate administrative revocation of a serial for a *malformed* cert for // an unspecified reason should fail because we can't attempt an akamai cache @@ -3771,8 +3765,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { test.AssertError(t, err, "Should be revoked") test.AssertContains(t, err.Error(), "already revoked") test.AssertEquals(t, len(mockSA.blocked), 0) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2) // Revoking a cert for key compromise with skipBlockKey set should work but // not block the key. @@ -3785,8 +3777,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { }) test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed") test.AssertEquals(t, len(mockSA.blocked), 0) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "keyCompromise"}, 1) // Revoking a cert for key compromise should work and block the key. mockSA.reset() @@ -3801,8 +3791,6 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { test.AssertEquals(t, mockSA.blocked[0].Source, "admin-revoker") test.AssertEquals(t, mockSA.blocked[0].Comment, "revoked by root") test.AssertEquals(t, mockSA.blocked[0].Added.AsTime(), clk.Now()) - test.AssertMetricWithLabelsEquals( - t, ra.revocationReasonCounter, prometheus.Labels{"reason": "keyCompromise"}, 2) // Revoking a malformed cert for key compromise should fail because we don't // have the pubkey to block. @@ -4081,3 +4069,60 @@ func TestUpdateRegistrationKey(t *testing.T) { test.AssertContains(t, err.Error(), "failed to update registration key") test.AssertContains(t, err.Error(), "mocked to always error") } + +func TestCRLShard(t *testing.T) { + var cdp []string + n, err := crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err != nil || n != 0 { + t.Errorf("crlShard(%+v) = %d, %s, want 0, nil", cdp, n, err) + } + + cdp = []string{ + "https://example.com/123.crl", + "https://example.net/123.crl", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err == nil { + t.Errorf("crlShard(%+v) = %d, %s, want 0, some error", cdp, n, err) + } + + cdp = []string{ + "https://example.com/abc", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err == nil { + t.Errorf("crlShard(%+v) = %d, %s, want 0, some error", cdp, n, err) + } + + cdp = []string{ + "example", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err == nil { + t.Errorf("crlShard(%+v) = %d, %s, want 0, some error", cdp, n, err) + } + + cdp = []string{ + "https://example.com/abc/-77.crl", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err == nil { + t.Errorf("crlShard(%+v) = %d, %s, want 0, some error", cdp, n, err) + } + + cdp = []string{ + "https://example.com/abc/123", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err != nil || n != 123 { + t.Errorf("crlShard(%+v) = %d, %s, want 123, nil", cdp, n, err) + } + + cdp = []string{ + "https://example.com/abc/123.crl", + } + n, err = crlShard(&x509.Certificate{CRLDistributionPoints: cdp}) + if err != nil || n != 123 { + t.Errorf("crlShard(%+v) = %d, %s, want 123, nil", cdp, n, err) + } +} diff --git a/sa/sa.go b/sa/sa.go index 09589fc6ee7..58a2423a79f 100644 --- a/sa/sa.go +++ b/sa/sa.go @@ -914,6 +914,9 @@ func addRevokedCertificate(ctx context.Context, tx db.Executor, req *sapb.Revoke // RevokeCertificate stores revocation information about a certificate. It will only store this // information if the certificate is not already marked as revoked. +// +// If ShardIdx is non-zero, RevokeCertificate also writes an entry for this certificate to +// the revokedCertificates table, with the provided shard number. func (ssa *SQLStorageAuthority) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) { if core.IsAnyNilOrZero(req.Serial, req.IssuerID, req.Date) { return nil, errIncompleteRequest