From ff877b1e047cfeb1845b731d9a272c39c8d0e747 Mon Sep 17 00:00:00 2001 From: marc Date: Mon, 16 Jul 2018 14:03:57 +0200 Subject: [PATCH] security: allow separate CA to verify client certificates. Part of #26630. This PR adds the following optional files in the certs directory: - `client.node.crt` (and associated `.key`): client certificate for the node user - `ca-client.crt`: certificate to verify client certificates This allows for split server/client certificates signed by different CAs. If `ca-client.crt` exists, it is used in the node's server-side TLS.Config CertPool for client certificate verification. Otherwise, we fall back on `ca.crt`. If `client.node.crt` exists, it is used in the node's client-side TLS.Config as the client certificate. Otherwise, we call back on `node.crt`. At load-time, we verify that the certificate to use contains `CN=node` and `ExtendedKeyUsage=ClientAuth`. Other bits in this PR: - add `cockroach cert create-client-ca` command - use client CA to sign client certs if present - show client CA on `cockroach cert list` - show all certs in debug page - metric for client CA and node client expiration times Release note (general change): allow separate CA for client certificates --- pkg/cli/cert.go | 50 +- pkg/cli/flags.go | 4 +- pkg/security/certificate_loader.go | 209 ++++--- pkg/security/certificate_loader_test.go | 69 ++- pkg/security/certificate_manager.go | 229 +++++-- pkg/security/certs.go | 58 +- pkg/security/certs_rotation_test.go | 6 +- pkg/security/certs_test.go | 285 ++++++++- pkg/security/tls.go | 30 +- pkg/security/tls_test.go | 1 + pkg/server/serverpb/status.pb.go | 583 +++++++++--------- pkg/server/serverpb/status.proto | 2 + pkg/server/status.go | 5 +- pkg/server/status_test.go | 4 +- pkg/sql/create_user.go | 17 +- .../reports/containers/certificates/index.tsx | 8 +- 16 files changed, 1097 insertions(+), 463 deletions(-) diff --git a/pkg/cli/cert.go b/pkg/cli/cert.go index e41f9657685f..e291aac8e887 100644 --- a/pkg/cli/cert.go +++ b/pkg/cli/cert.go @@ -69,6 +69,44 @@ func runCreateCACert(cmd *cobra.Command, args []string) error { "failed to generate CA cert and key") } +// A createClientCACert command generates a client CA certificate and stores it +// in the cert directory. +var createClientCACertCmd = &cobra.Command{ + Use: "create-client-ca --certs-dir= --ca-key=", + Short: "create client CA certificate and key", + Long: ` +Generate a client CA certificate "/ca-client.crt" and CA key "". +The certs directory is created if it does not exist. + +If the CA key exists and --allow-ca-key-reuse is true, the key is used. +If the CA certificate exists and --overwrite is true, the new CA certificate is prepended to it. + +The client CA is optional and should only be used when separate CAs are desired for server certificates +and client certificates. + +If the client CA exists, a client.node.crt client certificate must be created using: + cockroach cert create-client node + +Once the client.node.crt exists, all client certificates will be verified using the client CA. +`, + Args: cobra.NoArgs, + RunE: MaybeDecorateGRPCError(runCreateClientCACert), +} + +// runCreateClientCACert generates a key and CA certificate and writes them +// to their corresponding files. +func runCreateClientCACert(cmd *cobra.Command, args []string) error { + return errors.Wrap( + security.CreateClientCAPair( + baseCfg.SSLCertsDir, + baseCfg.SSLCAKey, + keySize, + caCertificateLifetime, + allowCAKeyReuse, + overwriteFiles), + "failed to generate client CA cert and key") +} + // A createNodeCert command generates a node certificate and stores it // in the cert directory. var createNodeCertCmd = &cobra.Command{ @@ -137,7 +175,8 @@ Creation fails if the CA expiration time is before the desired certificate expir func runCreateClientCert(cmd *cobra.Command, args []string) error { var err error var username string - if username, err = sql.NormalizeAndValidateUsername(args[0]); err != nil { + // We intentionally allow the `node` user to have a cert. + if username, err = sql.NormalizeAndValidateUsernameNoBlacklist(args[0]); err != nil { return errors.Wrap(err, "failed to generate client certificate and key") } @@ -200,6 +239,14 @@ func runListCerts(cmd *cobra.Command, args []string) error { addRow(cert, notes) } + if cert := cm.ClientCACert(); cert != nil { + var notes string + if cert.Error == nil && len(cert.ParsedCertificates) > 0 { + notes = fmt.Sprintf("num certs: %d", len(cert.ParsedCertificates)) + } + addRow(cert, notes) + } + if cert := cm.NodeCert(); cert != nil { var addresses []string if cert.Error == nil && len(cert.ParsedCertificates) > 0 { @@ -230,6 +277,7 @@ func runListCerts(cmd *cobra.Command, args []string) error { var certCmds = []*cobra.Command{ createCACertCmd, + createClientCACertCmd, createNodeCertCmd, createClientCertCmd, listCertsCmd, diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go index 5d858d025e75..f94b60895594 100644 --- a/pkg/cli/flags.go +++ b/pkg/cli/flags.go @@ -245,7 +245,7 @@ func init() { StringFlag(f, &baseCfg.SSLCertsDir, cliflags.CertsDir, baseCfg.SSLCertsDir) } - for _, cmd := range []*cobra.Command{createCACertCmd} { + for _, cmd := range []*cobra.Command{createCACertCmd, createClientCACertCmd} { f := cmd.Flags() // CA certificates have a longer expiration time. DurationFlag(f, &caCertificateLifetime, cliflags.CertificateLifetime, defaultCALifetime) @@ -259,7 +259,7 @@ func init() { } // The remaining flags are shared between all cert-generating functions. - for _, cmd := range []*cobra.Command{createCACertCmd, createNodeCertCmd, createClientCertCmd} { + for _, cmd := range []*cobra.Command{createCACertCmd, createClientCACertCmd, createNodeCertCmd, createClientCertCmd} { f := cmd.Flags() StringFlag(f, &baseCfg.SSLCAKey, cliflags.CAKey, baseCfg.SSLCAKey) IntFlag(f, &keySize, cliflags.KeySize, defaultKeySize) diff --git a/pkg/security/certificate_loader.go b/pkg/security/certificate_loader.go index 024508e5f963..822c7c025613 100644 --- a/pkg/security/certificate_loader.go +++ b/pkg/security/certificate_loader.go @@ -69,12 +69,15 @@ func ResetAssetLoader() { assetLoaderImpl = defaultAssetLoader } -type pemUsage uint32 +// PemUsage indicates the purpose of a given certificate. +type PemUsage uint32 const ( - _ pemUsage = iota - // CAPem describes a CA certificate. + _ PemUsage = iota + // CAPem describes the main CA certificate. CAPem + // ClientCAPem describes the CA certificate used to verify client certificates. + ClientCAPem // NodePem describes a combined server/client certificate for user Node. NodePem // ClientPem describes a client certificate. @@ -89,10 +92,16 @@ const ( defaultCertsDirPerm = 0700 ) -func (p pemUsage) String() string { +func isCA(usage PemUsage) bool { + return usage == CAPem || usage == ClientCAPem +} + +func (p PemUsage) String() string { switch p { case CAPem: - return "Certificate Authority" + return "CA" + case ClientCAPem: + return "Client CA" case NodePem: return "Node" case ClientPem: @@ -109,7 +118,7 @@ func (p pemUsage) String() string { // If Err != nil, the CertInfo must NOT be used. type CertInfo struct { // FileUsage describes the use of this certificate. - FileUsage pemUsage + FileUsage PemUsage // Filename is the base filename of the certificate. Filename string @@ -147,6 +156,55 @@ func isCertificateFile(filename string) bool { return strings.HasSuffix(filename, certExtension) } +// CertInfoFromFilename takes a filename and attempts to determine the +// certificate usage (ca, node, etc..). +func CertInfoFromFilename(filename string) (*CertInfo, error) { + parts := strings.Split(filename, `.`) + numParts := len(parts) + + if numParts < 2 { + return nil, errors.New("not enough parts found") + } + + var fileUsage PemUsage + var name string + prefix := parts[0] + switch parts[0] { + case `ca`: + // This is main CA certificate. + fileUsage = CAPem + if numParts != 2 { + return nil, errors.Errorf("CA certificate filename should match ca%s", certExtension) + } + case `ca-client`: + // This is client CA certificate. + fileUsage = ClientCAPem + if numParts != 2 { + return nil, errors.Errorf("client CA certificate filename should match ca-client%s", certExtension) + } + case `node`: + fileUsage = NodePem + if numParts != 2 { + return nil, errors.Errorf("node certificate filename should match node%s", certExtension) + } + case `client`: + fileUsage = ClientPem + // strip prefix and suffix and re-join middle parts. + name = strings.Join(parts[1:numParts-1], `.`) + if len(name) == 0 { + return nil, errors.Errorf("client certificate filename should match client.%s", certExtension) + } + default: + return nil, errors.Errorf("unknown prefix %q", prefix) + } + + return &CertInfo{ + FileUsage: fileUsage, + Filename: filename, + Name: name, + }, nil +} + // CertificateLoader searches for certificates and keys in the certs directory. type CertificateLoader struct { certsDir string @@ -236,12 +294,20 @@ func (cl *CertificateLoader) Load() error { } // Build the info struct from the filename. - ci, err := cl.certInfoFromFilename(filename) + ci, err := CertInfoFromFilename(filename) if err != nil { log.Warningf(context.Background(), "bad filename %s: %v", fullPath, err) continue } + // Read the cert file contents. + fullCertPath := filepath.Join(cl.certsDir, filename) + certPEMBlock, err := assetLoaderImpl.ReadFile(fullCertPath) + if err != nil { + log.Warningf(context.Background(), "could not read certificate file %s: %v", fullPath, err) + } + ci.FileContents = certPEMBlock + // Parse certificate, then look for the private key. // Errors are persisted for better visibility later. if err := parseCertificate(ci); err != nil { @@ -260,61 +326,11 @@ func (cl *CertificateLoader) Load() error { return nil } -// certInfoFromFilename takes a filename and attempts to determine the -// certificate usage (ca, node, etc..). -func (cl *CertificateLoader) certInfoFromFilename(filename string) (*CertInfo, error) { - parts := strings.Split(filename, `.`) - numParts := len(parts) - - if numParts < 2 { - return nil, errors.New("not enough parts found") - } - - var fileUsage pemUsage - var name string - prefix := parts[0] - switch parts[0] { - case `ca`: - fileUsage = CAPem - if numParts != 2 { - return nil, errors.Errorf("CA certificate filename should match ca%s", certExtension) - } - case `node`: - fileUsage = NodePem - if numParts != 2 { - return nil, errors.Errorf("node certificate filename should match node%s", certExtension) - } - case `client`: - fileUsage = ClientPem - // strip prefix and suffix and re-join middle parts. - name = strings.Join(parts[1:numParts-1], `.`) - if len(name) == 0 { - return nil, errors.Errorf("client certificate filename should match client.%s", certExtension) - } - default: - return nil, errors.Errorf("unknown prefix %q", prefix) - } - - // Read cert file contents. - fullCertPath := filepath.Join(cl.certsDir, filename) - certPEMBlock, err := assetLoaderImpl.ReadFile(fullCertPath) - if err != nil { - return nil, errors.Errorf("could not read certificate file: %v", err) - } - - return &CertInfo{ - FileUsage: fileUsage, - Filename: filename, - FileContents: certPEMBlock, - Name: name, - }, nil -} - // findKey takes a CertInfo and looks for the corresponding key file. // If found, sets the 'keyFilename' and returns nil, returns error otherwise. // Does not load CA keys. func (cl *CertificateLoader) findKey(ci *CertInfo) error { - if ci.FileUsage == CAPem { + if isCA(ci.FileUsage) { return nil } @@ -397,46 +413,65 @@ func parseCertificate(ci *CertInfo) error { return nil } -// validateCockroachCertificate takes a CertInfo and a parsed certificate and checks the -// values of certain fields. -func validateCockroachCertificate(ci *CertInfo, cert *x509.Certificate) error { +func hasKeyUsage(cert *x509.Certificate, usage x509.KeyUsage) bool { + return cert.KeyUsage&usage != 0 +} - hasKeyUsage := func(usage x509.KeyUsage) bool { - return cert.KeyUsage&usage != 0 +func hasExtendedKeyUsage(cert *x509.Certificate, usage x509.ExtKeyUsage) bool { + if cert.ExtKeyUsage == nil { + return false } - - hasExtendedKeyUsage := func(usage x509.ExtKeyUsage) bool { - if cert.ExtKeyUsage == nil { - return false - } - for _, u := range cert.ExtKeyUsage { - if u == usage { - return true - } + for _, u := range cert.ExtKeyUsage { + if u == usage { + return true } - return false + } + return false +} + +// validateDualPurposeNodeCert takes a CertInfo and a parsed certificate and checks the +// values of certain fields. +// This should only be called on the NodePem CertInfo when there is no specific +// client certificate for the 'node' user. +// Fields required for a valid server certificate are already checked. +func validateDualPurposeNodeCert(ci *CertInfo) error { + // The first certificate is used in client auth. + cert := ci.ParsedCertificates[0] + + // Check Subject Common Name. + if a, e := cert.Subject.CommonName, NodeUser; a != e { + return errors.Errorf("client/server node certificate has Subject \"CN=%s\", expected \"CN=%s\"", a, e) } + hasServer := hasExtendedKeyUsage(cert, x509.ExtKeyUsageServerAuth) + hasClient := hasExtendedKeyUsage(cert, x509.ExtKeyUsageClientAuth) + if !hasServer || !hasClient { + return errors.Errorf("client/server node certificate extended key usages: ServerAuth=%t, ClientAuth=%t, but both are needed", + hasServer, hasClient) + } + + return nil +} + +// validateCockroachCertificate takes a CertInfo and a parsed certificate and checks the +// values of certain fields. +func validateCockroachCertificate(ci *CertInfo, cert *x509.Certificate) error { + switch ci.FileUsage { case NodePem: - // Check Subject Common Name. - if a, e := cert.Subject.CommonName, NodeUser; a != e { - return errors.Errorf("node certificate has Subject \"CN=%s\", expected \"CN=%s\"", a, e) - } + // Common Name and ExtendedKeyUsage are checked only if there is no client certificate for 'node'. + // This is done in validateDualPurposeNodeCert. // Check key usages. - hasEncipherment := hasKeyUsage(x509.KeyUsageKeyEncipherment) - hasSignature := hasKeyUsage(x509.KeyUsageDigitalSignature) + hasEncipherment := hasKeyUsage(cert, x509.KeyUsageKeyEncipherment) + hasSignature := hasKeyUsage(cert, x509.KeyUsageDigitalSignature) if !hasEncipherment || !hasSignature { return errors.Errorf("node certificate key usages: KeyEncipherment=%t, DigitalSignature=%t, but both are needed", hasEncipherment, hasSignature) } - hasServer := hasExtendedKeyUsage(x509.ExtKeyUsageServerAuth) - hasClient := hasExtendedKeyUsage(x509.ExtKeyUsageClientAuth) - if !hasServer || !hasClient { - return errors.Errorf("node certificate extended key usages: ServerAuth=%t, ClientAuth=%t, but both are needed", - hasServer, hasClient) + if !hasExtendedKeyUsage(cert, x509.ExtKeyUsageServerAuth) { + return errors.Errorf("node certificate extended key usage missing ServerAuth") } case ClientPem: // Check that CommonName matches the username extracted from the filename. @@ -445,14 +480,14 @@ func validateCockroachCertificate(ci *CertInfo, cert *x509.Certificate) error { } // Check key usages. - hasEncipherment := hasKeyUsage(x509.KeyUsageKeyEncipherment) - hasSignature := hasKeyUsage(x509.KeyUsageDigitalSignature) + hasEncipherment := hasKeyUsage(cert, x509.KeyUsageKeyEncipherment) + hasSignature := hasKeyUsage(cert, x509.KeyUsageDigitalSignature) if !hasEncipherment || !hasSignature { return errors.Errorf("client certificate key usages: KeyEncipherment=%t, DigitalSignature=%t, but both are needed", hasEncipherment, hasSignature) } - if !hasExtendedKeyUsage(x509.ExtKeyUsageClientAuth) { + if !hasExtendedKeyUsage(cert, x509.ExtKeyUsageClientAuth) { return errors.Errorf("client certificate does not have ClientAuth extended key usage") } diff --git a/pkg/security/certificate_loader_test.go b/pkg/security/certificate_loader_test.go index 73d170640527..46dcf182413c 100644 --- a/pkg/security/certificate_loader_test.go +++ b/pkg/security/certificate_loader_test.go @@ -37,6 +37,55 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/timeutil" ) +func TestCertNomenclature(t *testing.T) { + defer leaktest.AfterTest(t)() + + // We're just testing nomenclature parsing, all files exist and contain a valid PEM block. + + testCases := []struct { + filename string + expectedError string + usage security.PemUsage + name string + }{ + // Test valid names. + {"ca.crt", "", security.CAPem, ""}, + {"ca-client.crt", "", security.ClientCAPem, ""}, + {"node.crt", "", security.NodePem, ""}, + {"client.root.crt", "", security.ClientPem, "root"}, + {"client.foo-bar.crt", "", security.ClientPem, "foo-bar"}, + {"client....foo.bar.baz.how.many.dots.do.you.need...really....crt", "", security.ClientPem, "...foo.bar.baz.how.many.dots.do.you.need...really..."}, + + // Bad names. This function is only called on filenames ending with '.crt'. + {"crt", "not enough parts found", 0, ""}, + {".crt", "unknown prefix", 0, ""}, + {"ca2.crt", "unknown prefix \"ca2\"", 0, ""}, + {"ca.client.crt", "CA certificate filename should match ca.crt", 0, ""}, + {"node2.crt", "unknown prefix \"node2\"", 0, ""}, + {"node.foo.crt", "node certificate filename should match node.crt", 0, ""}, + {"client2.crt", "unknown prefix \"client2\"", 0, ""}, + {"client.crt", "client certificate filename should match client..crt", 0, ""}, + {"root.crt", "unknown prefix \"root\"", 0, ""}, + } + + for i, tc := range testCases { + ci, err := security.CertInfoFromFilename(tc.filename) + if !testutils.IsError(err, tc.expectedError) { + t.Errorf("#%d: expected error %v, got %v", i, tc.expectedError, err) + continue + } + if err != nil { + continue + } + if ci.FileUsage != tc.usage { + t.Errorf("#%d: expected file usage %v, got %v", i, tc.usage, ci.FileUsage) + } + if ci.Name != tc.name { + t.Errorf("#%d: expected name %v, got %v", i, tc.name, ci.Name) + } + } +} + func TestLoadEmbeddedCerts(t *testing.T) { defer leaktest.AfterTest(t)() cl := security.NewCertificateLoader(security.EmbeddedCertsDir) @@ -245,18 +294,18 @@ func TestNamingScheme(t *testing.T) { }, }, { - // Bad CommonName. + // Bad CommonName: this is checked later in the CertificateManager. files: []testFile{ {"node.crt", 0777, badUserNodeCert}, - {"node.key", 0700, []byte{}}, + {"node.key", 0700, []byte("node.key")}, {"client.root.crt", 0777, notRootCert}, {"client.root.key", 0700, []byte{}}, }, certs: []security.CertInfo{ {FileUsage: security.ClientPem, Filename: "client.root.crt", Name: "root", Error: errors.New("client certificate has Subject \"CN=notroot\", expected \"CN=root")}, - {FileUsage: security.NodePem, Filename: "node.crt", - Error: errors.New("node certificate has Subject \"CN=notnode\", expected \"CN=node")}, + {FileUsage: security.NodePem, Filename: "node.crt", KeyFilename: "node.key", + FileContents: badUserNodeCert, KeyFileContents: []byte("node.key")}, }, }, { @@ -267,22 +316,22 @@ func TestNamingScheme(t *testing.T) { }, certs: []security.CertInfo{ {FileUsage: security.NodePem, Filename: "node.crt", - Error: errors.New("node certificate extended key usages: ServerAuth=false, ClientAuth=true, but both are needed")}, + Error: errors.New("node certificate extended key usage missing ServerAuth")}, }, }, { - // No ClientAuth key usage. + // No ClientAuth key usage: this is checked later by the CertificateManager. files: []testFile{ {"node.crt", 0777, noClientAuthNodeCert}, - {"node.key", 0700, []byte{}}, + {"node.key", 0700, []byte("node.key")}, {"client.root.crt", 0777, noClientAuthRootCert}, {"client.root.key", 0700, []byte{}}, }, certs: []security.CertInfo{ {FileUsage: security.ClientPem, Filename: "client.root.crt", Name: "root", Error: errors.New("client certificate does not have ClientAuth extended key usage")}, - {FileUsage: security.NodePem, Filename: "node.crt", - Error: errors.New("node certificate extended key usages: ServerAuth=true, ClientAuth=false, but both are needed")}, + {FileUsage: security.NodePem, Filename: "node.crt", KeyFilename: "node.key", + FileContents: noClientAuthNodeCert, KeyFileContents: []byte("node.key")}, }, }, { @@ -293,7 +342,7 @@ func TestNamingScheme(t *testing.T) { }, certs: []security.CertInfo{ {FileUsage: security.NodePem, Filename: "node.crt", - Error: errors.New("node certificate extended key usages: ServerAuth=false, ClientAuth=false, but both are needed")}, + Error: errors.New("node certificate extended key usage missing ServerAuth")}, }, }, { diff --git a/pkg/security/certificate_manager.go b/pkg/security/certificate_manager.go index 74ea6e49205a..92c01919b4a2 100644 --- a/pkg/security/certificate_manager.go +++ b/pkg/security/certificate_manager.go @@ -36,12 +36,24 @@ var ( Measurement: "Certificate Expiration", Unit: metric.Unit_TIMESTAMP_SEC, } + metaClientCAExpiration = metric.Metadata{ + Name: "security.certificate.expiration.client-ca", + Help: "Expiration for the client CA certificate. 0 means no certificate or error.", + Measurement: "Certificate Expiration", + Unit: metric.Unit_TIMESTAMP_SEC, + } metaNodeExpiration = metric.Metadata{ Name: "security.certificate.expiration.node", Help: "Expiration for the node certificate. 0 means no certificate or error.", Measurement: "Certificate Expiration", Unit: metric.Unit_TIMESTAMP_SEC, } + metaNodeClientExpiration = metric.Metadata{ + Name: "security.certificate.expiration.node-client", + Help: "Expiration for the node's client certificate. 0 means no certificate or error.", + Measurement: "Certificate Expiration", + Unit: metric.Unit_TIMESTAMP_SEC, + } ) // CertificateManager lives for the duration of the process and manages certificates and keys. @@ -51,6 +63,19 @@ var ( // Important note: Load() performs some sanity checks (file pairs match, CA certs don't disappear), // but these are by no means complete. Completeness is not required as nodes restarting have // no fallback if invalid certs/keys are present. +// +// The nomenclature for certificates is as follows, all within the certs-dir. +// - ca.crt main CA certificate. +// Used to verify everything unless overridden by more specifica CAs. +// - ca-client.crt CA certificate to verify client certificates. If it does not exist, +// fall back on 'ca.crt'. +// - node.crt node certificate. +// Server-side certificate (always) and client-side certificate unless +// client.node.crt is found. +// Verified using 'ca.crt'. +// - client..crt client certificate for 'user'. Verified using 'ca.crt', or 'ca-client.crt'. +// - client.node.crt client certificate for the 'node' user. If it does not exist, +// fall back on 'node.crt'. type CertificateManager struct { // Certificate directory is not modified after initialization. certsDir string @@ -65,9 +90,11 @@ type CertificateManager struct { initialized bool // Set of certs. These are swapped in during Load(), and never mutated afterwards. - caCert *CertInfo - nodeCert *CertInfo - clientCerts map[string]*CertInfo + caCert *CertInfo // default CA certificate + nodeCert *CertInfo // certificate for nodes (always server cert, sometimes client cert) + clientCACert *CertInfo // optional: certificate to verify client certificates + nodeClientCert *CertInfo // optional: client certificate for 'node' user. Also included in 'clientCerts'. + clientCerts map[string]*CertInfo // TLS configs. Initialized lazily. Wiped on every successful Load(). // Server-side config. @@ -81,16 +108,20 @@ type CertificateManager struct { // These are initialized when the certificate manager is created and updated // on reload. type CertificateMetrics struct { - CAExpiration *metric.Gauge - NodeExpiration *metric.Gauge + CAExpiration *metric.Gauge + ClientCAExpiration *metric.Gauge + NodeExpiration *metric.Gauge + NodeClientExpiration *metric.Gauge } func makeCertificateManager(certsDir string) *CertificateManager { cm := &CertificateManager{certsDir: os.ExpandEnv(certsDir)} // Initialize metrics: cm.certMetrics = CertificateMetrics{ - CAExpiration: metric.NewGauge(metaCAExpiration), - NodeExpiration: metric.NewGauge(metaNodeExpiration), + CAExpiration: metric.NewGauge(metaCAExpiration), + ClientCAExpiration: metric.NewGauge(metaClientCAExpiration), + NodeExpiration: metric.NewGauge(metaNodeExpiration), + NodeClientExpiration: metric.NewGauge(metaNodeClientExpiration), } return cm } @@ -145,6 +176,12 @@ func (cm *CertificateManager) CACertPath() string { return filepath.Join(cm.certsDir, "ca"+certExtension) } +// ClientCACertPath returns the expected file path for the CA certificate +// used to verify client certificates. +func (cm *CertificateManager) ClientCACertPath() string { + return filepath.Join(cm.certsDir, "ca-client"+certExtension) +} + // NodeCertPath returns the expected file path for the node certificate. func (cm *CertificateManager) NodeCertPath() string { return filepath.Join(cm.certsDir, "node"+certExtension) @@ -173,6 +210,14 @@ func (cm *CertificateManager) CACert() *CertInfo { return cm.caCert } +// ClientCACert returns the CA cert used to verify client certificates. May be nil. +// Callers should check for an internal Error field. +func (cm *CertificateManager) ClientCACert() *CertInfo { + cm.mu.RLock() + defer cm.mu.RUnlock() + return cm.clientCACert +} + // checkCertIsValid returns an error if the passed cert is missing or has an error. func checkCertIsValid(cert *CertInfo) error { if cert == nil { @@ -205,16 +250,21 @@ func (cm *CertificateManager) LoadCertificates() error { return errors.Wrapf(err, "problem loading certs directory %s", cm.certsDir) } - var caCert, nodeCert *CertInfo + var caCert, nodeCert, clientCACert, nodeClientCert *CertInfo clientCerts := make(map[string]*CertInfo) for _, ci := range cl.Certificates() { switch ci.FileUsage { case CAPem: caCert = ci + case ClientCAPem: + clientCACert = ci case NodePem: nodeCert = ci case ClientPem: clientCerts[ci.Name] = ci + if ci.Name == NodeUser { + nodeClientCert = ci + } } } @@ -228,12 +278,28 @@ func (cm *CertificateManager) LoadCertificates() error { if err := checkCertIsValid(nodeCert); checkCertIsValid(cm.nodeCert) == nil && err != nil { return errors.Wrap(err, "reload would lose valid node cert") } + if err := checkCertIsValid(nodeClientCert); checkCertIsValid(cm.nodeClientCert) == nil && err != nil { + return errors.Wrapf(err, "reload would lose valid client cert for '%s'", NodeUser) + } + if err := checkCertIsValid(clientCACert); checkCertIsValid(cm.clientCACert) == nil && err != nil { + return errors.Wrap(err, "reload would lose valid CA certificate for client verification") + } + } + + if nodeClientCert == nil && nodeCert != nil { + // No client certificate for node, but we have a node certificate. Check that + // it contains the required client fields. + if err := validateDualPurposeNodeCert(nodeCert); err != nil { + return err + } } // Swap everything. cm.caCert = caCert cm.nodeCert = nodeCert cm.clientCerts = clientCerts + cm.clientCACert = clientCACert + cm.nodeClientCert = nodeClientCert cm.initialized = true cm.serverConfig = nil @@ -249,43 +315,48 @@ func (cm *CertificateManager) LoadCertificates() error { // metric to zero. // cm.mu must be held to protect the certificates. Metrics do their own atomicity. func (cm *CertificateManager) updateMetricsLocked() { - // CA certificate expiration. - if cm.certMetrics.CAExpiration != nil { - if cm.caCert != nil && cm.caCert.Error == nil { - cm.certMetrics.CAExpiration.Update(cm.caCert.ExpirationTime.Unix()) + maybeSetMetric := func(m *metric.Gauge, ci *CertInfo) { + if m == nil { + return + } + if ci != nil && ci.Error == nil { + m.Update(ci.ExpirationTime.Unix()) } else { - cm.certMetrics.CAExpiration.Update(0) + m.Update(0) } } + // CA certificate expiration. + maybeSetMetric(cm.certMetrics.CAExpiration, cm.caCert) + + // Client CA certificate expiration. + maybeSetMetric(cm.certMetrics.ClientCAExpiration, cm.clientCACert) + // Node certificate expiration. // TODO(marc): we need to examine the entire certificate chain here, if the CA cert // used to sign the node cert expires sooner, then that is the expiration time to report. - if cm.certMetrics.NodeExpiration != nil { - if cm.nodeCert != nil && cm.nodeCert.Error == nil { - cm.certMetrics.NodeExpiration.Update(cm.nodeCert.ExpirationTime.Unix()) - } else { - cm.certMetrics.NodeExpiration.Update(0) - } - } + maybeSetMetric(cm.certMetrics.NodeExpiration, cm.nodeCert) + + // Node client certificate expiration. + maybeSetMetric(cm.certMetrics.NodeClientExpiration, cm.nodeClientCert) } // GetServerTLSConfig returns a server TLS config with a callback to fetch the // latest TLS config. We still attempt to get the config to make sure // the initial call has a valid config loaded. func (cm *CertificateManager) GetServerTLSConfig() (*tls.Config, error) { - if _, err := cm.GetEmbeddedServerTLSConfig(nil); err != nil { + if _, err := cm.getEmbeddedServerTLSConfig(nil); err != nil { return nil, err } return &tls.Config{ - GetConfigForClient: cm.GetEmbeddedServerTLSConfig, + GetConfigForClient: cm.getEmbeddedServerTLSConfig, }, nil } -// GetEmbeddedServerTLSConfig returns the most up-to-date server tls.Config. +// getEmbeddedServerTLSConfig returns the most up-to-date server tls.Config. // This is the callback set in tls.Config.GetConfigForClient. We currently // ignore the ClientHelloInfo object. -func (cm *CertificateManager) GetEmbeddedServerTLSConfig( +func (cm *CertificateManager) getEmbeddedServerTLSConfig( _ *tls.ClientHelloInfo, ) (*tls.Config, error) { cm.mu.Lock() @@ -295,17 +366,26 @@ func (cm *CertificateManager) GetEmbeddedServerTLSConfig( return cm.serverConfig, nil } - if err := checkCertIsValid(cm.caCert); err != nil { - return nil, errors.Wrap(err, "problem with CA certificate") + ca, err := cm.getCACertLocked() + if err != nil { + return nil, err } - if err := checkCertIsValid(cm.nodeCert); err != nil { - return nil, errors.Wrap(err, "problem with node certificate") + + nodeCert, err := cm.getNodeCertLocked() + if err != nil { + return nil, err + } + + clientCA, err := cm.getClientCACertLocked() + if err != nil { + return nil, err } cfg, err := newServerTLSConfig( - cm.nodeCert.FileContents, - cm.nodeCert.KeyFileContents, - cm.caCert.FileContents) + nodeCert.FileContents, + nodeCert.KeyFileContents, + ca.FileContents, + clientCA.FileContents) if err != nil { return nil, err } @@ -314,10 +394,43 @@ func (cm *CertificateManager) GetEmbeddedServerTLSConfig( return cfg, nil } -// getClientCertsLocked returns the client cert/key for the specified user, +// getCACertLocked returns the general CA cert. +// cm.mu must be held. +func (cm *CertificateManager) getCACertLocked() (*CertInfo, error) { + if err := checkCertIsValid(cm.caCert); err != nil { + return nil, errors.Wrap(err, "problem with CA certificate") + } + return cm.caCert, nil +} + +// getClientCACertLocked returns the CA cert used to verify client certificates. +// Use the client CA if it exists, otherwise fall back on the general CA. +// cm.mu must be held. +func (cm *CertificateManager) getClientCACertLocked() (*CertInfo, error) { + if cm.clientCACert == nil { + // No client CA: use general CA. + return cm.getCACertLocked() + } + + if err := checkCertIsValid(cm.clientCACert); err != nil { + return nil, errors.Wrap(err, "problem with client CA certificate") + } + return cm.clientCACert, nil +} + +// getNodeCertLocked returns the node certificate. +// cm.mu must be held. +func (cm *CertificateManager) getNodeCertLocked() (*CertInfo, error) { + if err := checkCertIsValid(cm.nodeCert); err != nil { + return nil, errors.Wrap(err, "problem with node certificate") + } + return cm.nodeCert, nil +} + +// getClientCertLocked returns the client cert/key for the specified user, // or an error if not found. // cm.mu must be held. -func (cm *CertificateManager) getClientCertsLocked(user string) (*CertInfo, error) { +func (cm *CertificateManager) getClientCertLocked(user string) (*CertInfo, error) { ci := cm.clientCerts[user] if err := checkCertIsValid(ci); err != nil { return nil, errors.Wrapf(err, "problem with client cert for user %s", user) @@ -326,29 +439,37 @@ func (cm *CertificateManager) getClientCertsLocked(user string) (*CertInfo, erro return ci, nil } -// getNodeClientCertsLocked returns the client cert/key for the node user. +// getNodeClientCertLocked returns the client cert/key for the node user. +// Use the client certificate for 'node' if it exists, otherwise use +// the node certificate which should be a combined client/server certificate. // cm.mu must be held. -func (cm *CertificateManager) getNodeClientCertsLocked() (*CertInfo, error) { - if err := checkCertIsValid(cm.nodeCert); err != nil { - return nil, errors.Wrap(err, "problem with node certificate") +func (cm *CertificateManager) getNodeClientCertLocked() (*CertInfo, error) { + if cm.nodeClientCert == nil { + // No specific client cert for 'node': use multi-purpose node cert. + return cm.getNodeCertLocked() } - return cm.nodeCert, nil + if err := checkCertIsValid(cm.nodeClientCert); err != nil { + return nil, errors.Wrap(err, "problem with node client certificate") + } + return cm.nodeClientCert, nil } -// GetClientTLSConfig returns the most up-to-date server tls.Config. -// Returns the dual-purpose node certs if user == NodeUser. +// GetClientTLSConfig returns the most up-to-date client tls.Config. +// Returns the dual-purpose node certs if user == NodeUser and there is no +// separate client cert for 'node'. func (cm *CertificateManager) GetClientTLSConfig(user string) (*tls.Config, error) { cm.mu.Lock() defer cm.mu.Unlock() // We always need the CA cert. - if err := checkCertIsValid(cm.caCert); err != nil { - return nil, errors.Wrap(err, "problem with CA certificate") + ca, err := cm.getCACertLocked() + if err != nil { + return nil, err } if user != NodeUser { - clientCert, err := cm.getClientCertsLocked(user) + clientCert, err := cm.getClientCertLocked(user) if err != nil { return nil, err } @@ -356,7 +477,7 @@ func (cm *CertificateManager) GetClientTLSConfig(user string) (*tls.Config, erro cfg, err := newClientTLSConfig( clientCert.FileContents, clientCert.KeyFileContents, - cm.caCert.FileContents) + ca.FileContents) if err != nil { return nil, err } @@ -365,12 +486,12 @@ func (cm *CertificateManager) GetClientTLSConfig(user string) (*tls.Config, erro } // We're the node user: - // Return the cache config if we have one. + // Return the cached config if we have one. if cm.clientConfig != nil { return cm.clientConfig, nil } - clientCert, err := cm.getNodeClientCertsLocked() + clientCert, err := cm.getNodeClientCertLocked() if err != nil { return nil, err } @@ -378,7 +499,7 @@ func (cm *CertificateManager) GetClientTLSConfig(user string) (*tls.Config, erro cfg, err := newClientTLSConfig( clientCert.FileContents, clientCert.KeyFileContents, - cm.caCert.FileContents) + ca.FileContents) if err != nil { return nil, err } @@ -398,9 +519,9 @@ func (cm *CertificateManager) GetClientCertPaths(user string) (string, string, e defer cm.mu.RUnlock() if user == NodeUser { - clientCert, err = cm.getNodeClientCertsLocked() + clientCert, err = cm.getNodeClientCertLocked() } else { - clientCert, err = cm.getClientCertsLocked(user) + clientCert, err = cm.getClientCertLocked(user) } if err != nil { return "", "", err @@ -416,11 +537,12 @@ func (cm *CertificateManager) GetCACertPath() (string, error) { cm.mu.RLock() defer cm.mu.RUnlock() - if err := checkCertIsValid(cm.caCert); err != nil { - return "", errors.Wrap(err, "problem with CA certificate") + ca, err := cm.getCACertLocked() + if err != nil { + return "", err } - return filepath.Join(cm.certsDir, cm.caCert.Filename), nil + return filepath.Join(cm.certsDir, ca.Filename), nil } // ListCertificates returns all loaded certificates, or an error if not yet initialized. @@ -436,6 +558,9 @@ func (cm *CertificateManager) ListCertificates() ([]*CertInfo, error) { if cm.caCert != nil { ret = append(ret, cm.caCert) } + if cm.clientCACert != nil { + ret = append(ret, cm.clientCACert) + } if cm.nodeCert != nil { ret = append(ret, cm.nodeCert) } diff --git a/pkg/security/certs.go b/pkg/security/certs.go index 6bcd853ccac7..95e74a929857 100644 --- a/pkg/security/certs.go +++ b/pkg/security/certs.go @@ -22,6 +22,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "fmt" "io/ioutil" "os" "time" @@ -68,13 +69,41 @@ func writeKeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) e return WritePEMToFile(keyFilePath, keyFileMode, overwrite, keyBlock) } -// CreateCAPair creates a CA key and a CA certificate. +// CreateCAPair creates a general CA certificate and associated key. +func CreateCAPair( + certsDir, caKeyPath string, + keySize int, + lifetime time.Duration, + allowKeyReuse bool, + overwrite bool, +) error { + return createCACertAndKey(certsDir, caKeyPath, CAPem, keySize, lifetime, allowKeyReuse, overwrite) +} + +// CreateClientCAPair creates a client CA certificate and associated key. +func CreateClientCAPair( + certsDir, caKeyPath string, + keySize int, + lifetime time.Duration, + allowKeyReuse bool, + overwrite bool, +) error { + return createCACertAndKey(certsDir, caKeyPath, ClientCAPem, keySize, lifetime, allowKeyReuse, overwrite) +} + +// createCACertAndKey creates a CA key and a CA certificate. // If the certs directory does not exist, it is created. // If the key does not exist, it is created. // The certificate is written to the certs directory. If the file already exists, // we append the original certificates to the new certificate. -func CreateCAPair( +// +// The filename of the certificate file must be specified. +// It should be one of: +// - ca.crt: the general CA certificate +// - ca-client.crt: the CA certificate to verify client certificates +func createCACertAndKey( certsDir, caKeyPath string, + caType PemUsage, keySize int, lifetime time.Duration, allowKeyReuse bool, @@ -86,6 +115,10 @@ func CreateCAPair( if len(certsDir) == 0 { return errors.New("the path to the certs directory is required") } + if caType != CAPem && caType != ClientCAPem { + return fmt.Errorf("caType argument to createCACertAndKey must be one of CAPem (%d), ClientCAPem (%d), got: %d", + CAPem, ClientCAPem, caType) + } // The certificate manager expands the env for the certs directory. // For consistency, we need to do this for the key as well. @@ -139,7 +172,14 @@ func CreateCAPair( return errors.Errorf("could not generate CA certificate: %v", err) } - certPath := cm.CACertPath() + var certPath string + // We've already checked the caType value at the beginning of this function. + switch caType { + case CAPem: + certPath = cm.CACertPath() + case ClientCAPem: + certPath = cm.ClientCACertPath() + } var existingCertificates []*pem.Block if _, err := os.Stat(certPath); err == nil { @@ -230,6 +270,7 @@ func CreateNodePair( // CreateClientPair creates a node key and certificate. // The CA cert and key must load properly. If multiple certificates // exist in the CA cert, the first one is used. +// If a client CA exists, this is used instead. func CreateClientPair( certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, user string, ) error { @@ -250,8 +291,17 @@ func CreateClientPair( return err } + var caCertPath string + // Check to see if we are using a client CA. + // We only check for its presence, not whether it has errors. + if cm.ClientCACert() != nil { + caCertPath = cm.ClientCACertPath() + } else { + caCertPath = cm.CACertPath() + } + // Load the CA pair. - caCert, caPrivateKey, err := loadCACertAndKey(cm.CACertPath(), caKeyPath) + caCert, caPrivateKey, err := loadCACertAndKey(caCertPath, caKeyPath) if err != nil { return err } diff --git a/pkg/security/certs_rotation_test.go b/pkg/security/certs_rotation_test.go index 3195d841bcc6..655240687ea3 100644 --- a/pkg/security/certs_rotation_test.go +++ b/pkg/security/certs_rotation_test.go @@ -52,7 +52,7 @@ func TestRotateCerts(t *testing.T) { } }() - if err := generateAllCerts(certsDir); err != nil { + if err := generateBaseCerts(certsDir); err != nil { t.Fatal(err) } @@ -102,7 +102,7 @@ func TestRotateCerts(t *testing.T) { if err := os.RemoveAll(certsDir); err != nil { t.Fatal(err) } - if err := generateAllCerts(certsDir); err != nil { + if err := generateBaseCerts(certsDir); err != nil { t.Fatal(err) } @@ -149,7 +149,7 @@ func TestRotateCerts(t *testing.T) { if err := os.Remove(filepath.Join(certsDir, security.EmbeddedCAKey)); err != nil { t.Fatal(err) } - if err := generateAllCerts(certsDir); err != nil { + if err := generateBaseCerts(certsDir); err != nil { t.Fatal(err) } diff --git a/pkg/security/certs_test.go b/pkg/security/certs_test.go index 6efe37895fd8..718178f46bd2 100644 --- a/pkg/security/certs_test.go +++ b/pkg/security/certs_test.go @@ -16,6 +16,8 @@ package security_test import ( "context" + gosql "database/sql" + "fmt" "io/ioutil" "net/http" "os" @@ -147,7 +149,11 @@ func TestGenerateNodeCerts(t *testing.T) { } } -func generateAllCerts(certsDir string) error { +// Generate basic certs: +// ca.crt: CA certificate +// node.crt: dual-purpose node certificate +// client.root.crt: client certificate for the root user. +func generateBaseCerts(certsDir string) error { if err := security.CreateCAPair( certsDir, filepath.Join(certsDir, security.EmbeddedCAKey), 512, time.Hour*96, true, true, @@ -172,6 +178,51 @@ func generateAllCerts(certsDir string) error { return nil } +// Generate certificates with separate CAs: +// ca.crt: CA certificate +// ca-client.crt: CA certificate to verify client certs +// node.crt: node server cert: signed by ca.crt +// client.node.crt: node client cert: signed by ca-client.crt +// client.root.crt: root client cert: signed by ca-client.crt +func generateSplitCACerts(certsDir string) error { + if err := security.CreateCAPair( + certsDir, filepath.Join(certsDir, security.EmbeddedCAKey), + 512, time.Hour*96, true, true, + ); err != nil { + return errors.Errorf("could not generate CA pair: %v", err) + } + + if err := security.CreateNodePair( + certsDir, filepath.Join(certsDir, security.EmbeddedCAKey), + 512, time.Hour*48, true, []string{"127.0.0.1"}, + ); err != nil { + return errors.Errorf("could not generate Node pair: %v", err) + } + + if err := security.CreateClientCAPair( + certsDir, filepath.Join(certsDir, security.EmbeddedClientCAKey), + 512, time.Hour*96, true, true, + ); err != nil { + return errors.Errorf("could not generate CA pair: %v", err) + } + + if err := security.CreateClientPair( + certsDir, filepath.Join(certsDir, security.EmbeddedClientCAKey), + 512, time.Hour*48, true, security.NodeUser, + ); err != nil { + return errors.Errorf("could not generate Client pair: %v", err) + } + + if err := security.CreateClientPair( + certsDir, filepath.Join(certsDir, security.EmbeddedClientCAKey), + 512, time.Hour*48, true, security.RootUser, + ); err != nil { + return errors.Errorf("could not generate Client pair: %v", err) + } + + return nil +} + // This is a fairly high-level test of CA and node certificates. // We construct SSL server and clients and use the generated certs. func TestUseCerts(t *testing.T) { @@ -189,12 +240,13 @@ func TestUseCerts(t *testing.T) { } }() - if err := generateAllCerts(certsDir); err != nil { + if err := generateBaseCerts(certsDir); err != nil { t.Fatal(err) } // Load TLS Configs. This is what TestServer and HTTPClient do internally. if _, err := security.LoadServerTLSConfig( + filepath.Join(certsDir, security.EmbeddedCACert), filepath.Join(certsDir, security.EmbeddedCACert), filepath.Join(certsDir, security.EmbeddedNodeCert), filepath.Join(certsDir, security.EmbeddedNodeKey), @@ -218,7 +270,93 @@ func TestUseCerts(t *testing.T) { SSLCertsDir: certsDir, DisableWebSessionAuthentication: true, } - s, _, _ := serverutils.StartServer(t, params) + s, _, db := serverutils.StartServer(t, params) + defer s.Stopper().Stop(context.TODO()) + + // Insecure mode. + clientContext := testutils.NewNodeTestBaseContext() + clientContext.Insecure = true + httpClient, err := clientContext.GetHTTPClient() + if err != nil { + t.Fatal(err) + } + req, err := http.NewRequest("GET", s.AdminURL()+"/_status/metrics/local", nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + resp, err := httpClient.Do(req) + if err == nil { + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + t.Fatalf("Expected SSL error, got success: %s", body) + } + + // New client. With certs this time. + clientContext = testutils.NewNodeTestBaseContext() + clientContext.SSLCertsDir = certsDir + httpClient, err = clientContext.GetHTTPClient() + if err != nil { + t.Fatalf("Expected success, got %v", err) + } + req, err = http.NewRequest("GET", s.AdminURL()+"/_status/metrics/local", nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + resp, err = httpClient.Do(req) + if err != nil { + t.Fatalf("Expected success, got %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(resp.Body) + t.Fatalf("Expected OK, got %q with body: %s", resp.Status, body) + } + + // Check KV connection. + if err := db.Put(context.Background(), "foo", "bar"); err != nil { + t.Error(err) + } +} + +func makeSecurePGUrl(addr, user, certsDir, caName, certName, keyName string) string { + return fmt.Sprintf("postgresql://%s@%s/?sslmode=verify-full&sslrootcert=%s&sslcert=%s&sslkey=%s", + user, addr, + filepath.Join(certsDir, caName), + filepath.Join(certsDir, certName), + filepath.Join(certsDir, keyName)) +} + +// This is a fairly high-level test of CA and node certificates. +// We construct SSL server and clients and use the generated certs. +func TestUseSplitCACerts(t *testing.T) { + defer leaktest.AfterTest(t)() + // Do not mock cert access for this test. + security.ResetAssetLoader() + defer ResetTest() + certsDir, err := ioutil.TempDir("", "certs_test") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.RemoveAll(certsDir); err != nil { + t.Fatal(err) + } + }() + + if err := generateSplitCACerts(certsDir); err != nil { + t.Fatal(err) + } + + // Start a test server and override certs. + // We use a real context since we want generated certs. + // Web session authentication is disabled in order to avoid the need to + // authenticate the individual clients being instantiated (session auth has + // no effect on what is being tested here). + params := base.TestServerArgs{ + SSLCertsDir: certsDir, + DisableWebSessionAuthentication: true, + } + s, _, db := serverutils.StartServer(t, params) defer s.Stopper().Stop(context.TODO()) // Insecure mode. @@ -259,4 +397,145 @@ func TestUseCerts(t *testing.T) { body, _ := ioutil.ReadAll(resp.Body) t.Fatalf("Expected OK, got %q with body: %s", resp.Status, body) } + + // Check KV connection. + if err := db.Put(context.Background(), "foo", "bar"); err != nil { + t.Error(err) + } + + // Test a SQL client with various certificates. + testCases := []struct { + user, caName, certPrefix string + expectedError string + }{ + // Success, but "node" is not a sql user. + {"node", security.EmbeddedCACert, "client.node", "pq: user node does not exist"}, + // Success! + {"root", security.EmbeddedCACert, "client.root", ""}, + // Bad server CA: can't verify server certificate. + {"root", security.EmbeddedClientCACert, "client.root", "certificate signed by unknown authority"}, + // Bad client cert: we're using the node cert but it's not signed by the client CA. + {"node", security.EmbeddedCACert, "node", "tls: bad certificate"}, + } + + for i, tc := range testCases { + pgUrl := makeSecurePGUrl(s.ServingAddr(), tc.user, certsDir, tc.caName, tc.certPrefix+".crt", tc.certPrefix+".key") + goDB, err := gosql.Open("postgres", pgUrl) + if err != nil { + t.Fatal(err) + } + defer goDB.Close() + + _, err = goDB.Exec("SELECT 1") + if !testutils.IsError(err, tc.expectedError) { + t.Errorf("#%d: expected error %v, got %v", i, tc.expectedError, err) + } + } +} + +// This is a fairly high-level test of CA and node certificates. +// We construct SSL server and clients and use the generated certs. +func TestUseWrongSplitCACerts(t *testing.T) { + defer leaktest.AfterTest(t)() + // Do not mock cert access for this test. + security.ResetAssetLoader() + defer ResetTest() + certsDir, err := ioutil.TempDir("", "certs_test") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.RemoveAll(certsDir); err != nil { + t.Fatal(err) + } + }() + + if err := generateSplitCACerts(certsDir); err != nil { + t.Fatal(err) + } + + // Delete the ca-client.crt before starting the node. + // This will make the server fall back on using ca.crt. + if err := os.Remove(filepath.Join(certsDir, "ca-client.crt")); err != nil { + t.Fatal(err) + } + + // Start a test server and override certs. + // We use a real context since we want generated certs. + // Web session authentication is disabled in order to avoid the need to + // authenticate the individual clients being instantiated (session auth has + // no effect on what is being tested here). + params := base.TestServerArgs{ + SSLCertsDir: certsDir, + DisableWebSessionAuthentication: true, + } + s, _, db := serverutils.StartServer(t, params) + defer s.Stopper().Stop(context.TODO()) + + // Insecure mode. + clientContext := testutils.NewNodeTestBaseContext() + clientContext.Insecure = true + httpClient, err := clientContext.GetHTTPClient() + if err != nil { + t.Fatal(err) + } + req, err := http.NewRequest("GET", s.AdminURL()+"/_status/metrics/local", nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + resp, err := httpClient.Do(req) + if err == nil { + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + t.Fatalf("Expected SSL error, got success: %s", body) + } + + // New client. With certs this time. + clientContext = testutils.NewNodeTestBaseContext() + clientContext.SSLCertsDir = certsDir + httpClient, err = clientContext.GetHTTPClient() + if err != nil { + t.Fatalf("Expected success, got %v", err) + } + req, err = http.NewRequest("GET", s.AdminURL()+"/_status/metrics/local", nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + // TODO(mberhault): this will succeed once the UI TLSConfig does not + // check client certificates. + _, err = httpClient.Do(req) + if expected := "tls: bad certificate"; !testutils.IsError(err, expected) { + t.Errorf("expected error %v, got %v", expected, err) + } + + // Check KV connection. + if err := db.Put(context.Background(), "foo", "bar"); err != nil { + t.Error(err) + } + + // Try with various certificates. + testCases := []struct { + user, caName, certPrefix string + expectedError string + }{ + // Certificate signed by wrong client CA. + {"root", security.EmbeddedCACert, "client.root", "tls: bad certificate"}, + // Success! The node certificate still contains "CN=node" and is signed by ca.crt. + {"node", security.EmbeddedCACert, "node", "pq: user node does not exist"}, + } + + for i, tc := range testCases { + pgUrl := makeSecurePGUrl(s.ServingAddr(), tc.user, certsDir, tc.caName, tc.certPrefix+".crt", tc.certPrefix+".key") + goDB, err := gosql.Open("postgres", pgUrl) + if err != nil { + t.Fatal(err) + } + defer goDB.Close() + + _, err = goDB.Exec("SELECT 1") + if !testutils.IsError(err, tc.expectedError) { + t.Errorf("#%d: expected error %v, got %v", i, tc.expectedError, err) + } + } + } diff --git a/pkg/security/tls.go b/pkg/security/tls.go index 5601fb297274..6fb35defbdbb 100644 --- a/pkg/security/tls.go +++ b/pkg/security/tls.go @@ -30,6 +30,8 @@ const ( EmbeddedCertsDir = "test_certs" EmbeddedCACert = "ca.crt" EmbeddedCAKey = "ca.key" + EmbeddedClientCACert = "ca-client.crt" + EmbeddedClientCAKey = "ca-client.key" EmbeddedNodeCert = "node.crt" EmbeddedNodeKey = "node.key" EmbeddedRootCert = "client.root.crt" @@ -41,10 +43,12 @@ const ( // LoadServerTLSConfig creates a server TLSConfig by loading the CA and server certs. // The following paths must be passed: // - sslCA: path to the CA certificate +// - sslClientCA: path to the CA certificate to verify client certificates, +// can be the same as sslCA // - sslCert: path to the server certificate // - sslCertKey: path to the server key // If the path is prefixed with "embedded=", load the embedded certs. -func LoadServerTLSConfig(sslCA, sslCert, sslCertKey string) (*tls.Config, error) { +func LoadServerTLSConfig(sslCA, sslClientCA, sslCert, sslCertKey string) (*tls.Config, error) { certPEM, err := assetLoaderImpl.ReadFile(sslCert) if err != nil { return nil, err @@ -57,20 +61,36 @@ func LoadServerTLSConfig(sslCA, sslCert, sslCertKey string) (*tls.Config, error) if err != nil { return nil, err } - return newServerTLSConfig(certPEM, keyPEM, caPEM) + clientCAPEM, err := assetLoaderImpl.ReadFile(sslClientCA) + if err != nil { + return nil, err + } + return newServerTLSConfig(certPEM, keyPEM, caPEM, clientCAPEM) } // newServerTLSConfig creates a server TLSConfig from the supplied byte strings containing // - the certificate of this node (should be signed by the CA), // - the private key of this node. -// - the certificate of the cluster CA, -func newServerTLSConfig(certPEM, keyPEM, caPEM []byte) (*tls.Config, error) { +// - the certificate of the cluster CA, used to verify other server certificates +// - the certificate of the client CA, used to verify client certificates +// +// caClientPEM can be equal to caPEM (shared CA) or nil (use system CA pool). +func newServerTLSConfig(certPEM, keyPEM, caPEM, caClientPEM []byte) (*tls.Config, error) { cfg, err := newBaseTLSConfig(certPEM, keyPEM, caPEM) if err != nil { return nil, err } cfg.ClientAuth = tls.VerifyClientCertIfGiven - cfg.ClientCAs = cfg.RootCAs + + if caClientPEM != nil { + certPool := x509.NewCertPool() + + if !certPool.AppendCertsFromPEM(caClientPEM) { + return nil, errors.Errorf("failed to parse client CA PEM data to pool") + } + cfg.ClientCAs = certPool + } + // Use the default cipher suite from golang (RC4 is going away in 1.5). // Prefer the server-specified suite. cfg.PreferServerCipherSuites = true diff --git a/pkg/security/tls_test.go b/pkg/security/tls_test.go index 02de7d9f80c9..b9bb1536ecb1 100644 --- a/pkg/security/tls_test.go +++ b/pkg/security/tls_test.go @@ -26,6 +26,7 @@ import ( func TestLoadTLSConfig(t *testing.T) { defer leaktest.AfterTest(t)() config, err := security.LoadServerTLSConfig( + filepath.Join(security.EmbeddedCertsDir, security.EmbeddedCACert), filepath.Join(security.EmbeddedCertsDir, security.EmbeddedCACert), filepath.Join(security.EmbeddedCertsDir, security.EmbeddedNodeCert), filepath.Join(security.EmbeddedCertsDir, security.EmbeddedNodeKey)) diff --git a/pkg/server/serverpb/status.pb.go b/pkg/server/serverpb/status.pb.go index bcb820fd5a2c..258479df1e52 100644 --- a/pkg/server/serverpb/status.pb.go +++ b/pkg/server/serverpb/status.pb.go @@ -43,17 +43,23 @@ var _ = time.Kitchen type CertificateDetails_CertificateType int32 const ( - CertificateDetails_CA CertificateDetails_CertificateType = 0 - CertificateDetails_NODE CertificateDetails_CertificateType = 1 + CertificateDetails_CA CertificateDetails_CertificateType = 0 + CertificateDetails_NODE CertificateDetails_CertificateType = 1 + CertificateDetails_CLIENT_CA CertificateDetails_CertificateType = 2 + CertificateDetails_CLIENT CertificateDetails_CertificateType = 3 ) var CertificateDetails_CertificateType_name = map[int32]string{ 0: "CA", 1: "NODE", + 2: "CLIENT_CA", + 3: "CLIENT", } var CertificateDetails_CertificateType_value = map[string]int32{ - "CA": 0, - "NODE": 1, + "CA": 0, + "NODE": 1, + "CLIENT_CA": 2, + "CLIENT": 3, } func (x CertificateDetails_CertificateType) String() string { @@ -14324,288 +14330,289 @@ var ( func init() { proto.RegisterFile("server/serverpb/status.proto", fileDescriptorStatus) } var fileDescriptorStatus = []byte{ - // 4521 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x7b, 0x5d, 0x6c, 0x1c, 0x47, - 0x72, 0xbf, 0x86, 0xdc, 0xcf, 0x5a, 0x7e, 0x2c, 0x9b, 0x14, 0xb5, 0x5a, 0xeb, 0xb8, 0xf2, 0xc8, - 0x96, 0x29, 0x59, 0xde, 0xb5, 0x79, 0xf6, 0x1f, 0xfa, 0x2b, 0xf1, 0xd9, 0xfc, 0x92, 0x44, 0x49, - 0x96, 0xa8, 0xa1, 0xe4, 0x0b, 0x94, 0x83, 0x07, 0xc3, 0x9d, 0xe6, 0x72, 0x8e, 0xb3, 0x33, 0xc3, - 0x99, 0x59, 0x86, 0x7b, 0x86, 0x2e, 0x8e, 0x83, 0x04, 0xf9, 0xc0, 0x5d, 0xee, 0xf2, 0x85, 0x03, - 0x82, 0x00, 0xc1, 0x3d, 0x24, 0xc1, 0x01, 0x09, 0x12, 0xe4, 0x25, 0x01, 0x02, 0x04, 0xc8, 0x43, - 0xe0, 0xb7, 0x24, 0x48, 0x10, 0x04, 0x09, 0x40, 0x27, 0xbc, 0x3c, 0x24, 0x40, 0x1e, 0xf3, 0x74, - 0x4f, 0x41, 0x57, 0xf7, 0xcc, 0xf6, 0xec, 0xae, 0x76, 0x97, 0xa2, 0x75, 0xc8, 0x83, 0xb4, 0xd3, - 0xd5, 0x55, 0xd5, 0xbf, 0xae, 0xee, 0xae, 0xae, 0xae, 0x6e, 0xc2, 0x85, 0x80, 0xfa, 0x07, 0xd4, - 0xaf, 0xf1, 0x1f, 0x6f, 0xbb, 0x16, 0x84, 0x46, 0xd8, 0x0a, 0xaa, 0x9e, 0xef, 0x86, 0x2e, 0x39, - 0x5f, 0x77, 0xeb, 0x7b, 0xbe, 0x6b, 0xd4, 0x77, 0xab, 0x9c, 0xa1, 0x1a, 0xf1, 0x95, 0x8b, 0xdb, - 0x2d, 0xcb, 0x36, 0x6b, 0x96, 0xb3, 0xe3, 0x72, 0xe6, 0xf2, 0x6c, 0xc3, 0x0d, 0x02, 0xcb, 0xab, - 0xf1, 0x1f, 0x41, 0x3c, 0x87, 0xd2, 0xde, 0x76, 0xcd, 0xf0, 0x3c, 0x9d, 0xe9, 0x16, 0xaa, 0xcb, - 0x24, 0xaa, 0x30, 0x8d, 0xd0, 0x10, 0xb4, 0xcb, 0x02, 0x8c, 0x69, 0x19, 0x0d, 0xc7, 0x0d, 0x42, - 0xab, 0x1e, 0x30, 0x86, 0x4e, 0x49, 0xf0, 0x95, 0x23, 0xd0, 0x88, 0x35, 0x01, 0xb9, 0xac, 0x06, - 0xa1, 0xeb, 0x1b, 0x0d, 0x5a, 0xa3, 0x4e, 0xc3, 0x72, 0xa2, 0x1f, 0x6f, 0xbb, 0xd6, 0x3c, 0xa8, - 0xd7, 0x63, 0x79, 0xc1, 0x63, 0x53, 0x23, 0xa0, 0x7a, 0x42, 0xbe, 0x12, 0xd5, 0x89, 0xdf, 0x6d, - 0x23, 0xa0, 0xd8, 0x02, 0x8d, 0x80, 0xb7, 0x42, 0xcb, 0xae, 0xd9, 0x6e, 0x83, 0xfd, 0x8b, 0x14, - 0x22, 0xad, 0xe5, 0xf8, 0x34, 0x70, 0xed, 0x03, 0x6a, 0xea, 0x86, 0x69, 0xfa, 0xa2, 0xee, 0x25, - 0x1a, 0xd6, 0xcd, 0x9a, 0x6f, 0xec, 0x84, 0xf8, 0x9f, 0xb7, 0x8d, 0x3f, 0xa2, 0x72, 0xae, 0xe1, - 0x36, 0x5c, 0xfc, 0xac, 0xb1, 0x2f, 0x41, 0xbd, 0xd0, 0x70, 0xdd, 0x86, 0x4d, 0x6b, 0x86, 0x67, - 0xd5, 0x0c, 0xc7, 0x71, 0x43, 0x23, 0xb4, 0x5c, 0x27, 0x46, 0x28, 0x6a, 0xb1, 0xb4, 0xdd, 0xda, - 0xa9, 0x85, 0x56, 0x93, 0x06, 0xa1, 0xd1, 0x14, 0x36, 0x57, 0xab, 0x30, 0xbb, 0x4a, 0xfd, 0xd0, - 0xda, 0xb1, 0xea, 0x46, 0x48, 0x03, 0x8d, 0xee, 0xb7, 0x68, 0x10, 0x92, 0x73, 0x90, 0x75, 0x5c, - 0x93, 0xea, 0x96, 0x59, 0x52, 0x2e, 0x2a, 0x8b, 0x79, 0x2d, 0xc3, 0x8a, 0x1b, 0xa6, 0xfa, 0xb7, - 0x29, 0x20, 0x92, 0xc0, 0x1a, 0x0d, 0x0d, 0xcb, 0x0e, 0xc8, 0x43, 0x48, 0x85, 0x6d, 0x8f, 0x22, - 0xf3, 0xd4, 0xd2, 0xbb, 0xd5, 0x67, 0xce, 0x85, 0x6a, 0xaf, 0xb0, 0x4c, 0x7a, 0xd4, 0xf6, 0xa8, - 0x86, 0xaa, 0xc8, 0x25, 0x98, 0xa4, 0xbe, 0xef, 0xfa, 0x7a, 0x93, 0x06, 0x81, 0xd1, 0xa0, 0xa5, - 0x31, 0x04, 0x32, 0x81, 0xc4, 0x0f, 0x38, 0x8d, 0x10, 0x48, 0xb1, 0x39, 0x51, 0x1a, 0xbf, 0xa8, - 0x2c, 0x4e, 0x68, 0xf8, 0x4d, 0x34, 0xc8, 0xec, 0x58, 0xd4, 0x36, 0x83, 0x52, 0xea, 0xe2, 0xf8, - 0x62, 0x61, 0xe9, 0xed, 0x93, 0xa1, 0xb9, 0x89, 0xb2, 0x2b, 0xa9, 0xcf, 0x8e, 0x2a, 0x67, 0x34, - 0xa1, 0xa9, 0xfc, 0x67, 0x63, 0x90, 0xe1, 0x15, 0x64, 0x1e, 0x32, 0x56, 0x10, 0xb4, 0xa8, 0x1f, - 0x59, 0x86, 0x97, 0x48, 0x09, 0xb2, 0x41, 0x6b, 0xfb, 0xeb, 0xb4, 0x1e, 0x0a, 0xa4, 0x51, 0x91, - 0x7c, 0x09, 0xe0, 0xc0, 0xb0, 0x2d, 0x53, 0xdf, 0xf1, 0xdd, 0x26, 0x42, 0x1d, 0xd7, 0xf2, 0x48, - 0xb9, 0xe9, 0xbb, 0x4d, 0x52, 0x81, 0x02, 0xaf, 0x6e, 0x39, 0xa1, 0x65, 0x97, 0x52, 0x58, 0xcf, - 0x25, 0x1e, 0x33, 0x0a, 0xb9, 0x00, 0x79, 0x36, 0x47, 0x68, 0x10, 0xd0, 0xa0, 0x94, 0xbe, 0x38, - 0xbe, 0x98, 0xd7, 0x3a, 0x04, 0x52, 0x83, 0xd9, 0xc0, 0x6a, 0x38, 0x46, 0xd8, 0xf2, 0xa9, 0x6e, - 0xd8, 0x0d, 0xd7, 0xb7, 0xc2, 0xdd, 0x66, 0x29, 0x83, 0x18, 0x48, 0x5c, 0xb5, 0x1c, 0xd5, 0x30, - 0x38, 0x5e, 0x6b, 0xdb, 0xb6, 0xea, 0xfa, 0x1e, 0x6d, 0x97, 0xb2, 0xc8, 0x97, 0xe7, 0x94, 0xbb, - 0xb4, 0x4d, 0x5e, 0x82, 0xfc, 0x1e, 0x6d, 0xeb, 0x2d, 0xb4, 0x79, 0x0e, 0x5b, 0xcb, 0xed, 0xd1, - 0xf6, 0x63, 0xb4, 0xf7, 0x35, 0x20, 0xf4, 0x30, 0xa4, 0x8e, 0x49, 0x4d, 0xbd, 0xc3, 0x95, 0x47, - 0xae, 0x62, 0x54, 0x73, 0x57, 0x70, 0xab, 0x97, 0x60, 0xba, 0x6b, 0x6c, 0x49, 0x06, 0xc6, 0x56, - 0x97, 0x8b, 0x67, 0x48, 0x0e, 0x52, 0xf7, 0x1f, 0xac, 0xad, 0x17, 0x15, 0xd5, 0x85, 0xb9, 0xe4, - 0x0c, 0x0c, 0x3c, 0xd7, 0x09, 0x28, 0xf9, 0x2a, 0x4c, 0xd4, 0x25, 0x7a, 0x49, 0xc1, 0xc1, 0x7c, - 0xe3, 0x44, 0x83, 0x29, 0x46, 0x31, 0xa1, 0x48, 0x7d, 0x0f, 0xa6, 0x44, 0xf5, 0xb0, 0xd9, 0x4e, - 0xe6, 0x20, 0xed, 0x53, 0xc3, 0x6c, 0xe3, 0x88, 0xe6, 0x34, 0x5e, 0x50, 0xff, 0x4b, 0x81, 0xe9, - 0x58, 0x83, 0x40, 0xfb, 0x24, 0xa9, 0x22, 0xbd, 0xb2, 0x7c, 0x7c, 0x54, 0xc9, 0xdc, 0x67, 0x6a, - 0xd6, 0x7e, 0x74, 0x54, 0xf9, 0x72, 0xc3, 0x0a, 0x77, 0x5b, 0xdb, 0xd5, 0xba, 0xdb, 0xac, 0xc5, - 0x1d, 0x30, 0xb7, 0x3b, 0xdf, 0x35, 0x6f, 0xaf, 0x51, 0x13, 0x6e, 0xae, 0xca, 0xc5, 0x62, 0x14, - 0x5f, 0x81, 0xac, 0x18, 0x6e, 0xc4, 0x51, 0x58, 0x5a, 0x90, 0x8c, 0xc0, 0xbc, 0x49, 0xf5, 0x71, - 0xec, 0x4d, 0x96, 0x4d, 0xd3, 0x17, 0xbd, 0x8e, 0x84, 0xc8, 0x0d, 0x00, 0x74, 0xc0, 0x3a, 0x73, - 0xc0, 0x38, 0xff, 0x0a, 0x4b, 0x67, 0x25, 0x15, 0x58, 0x59, 0xdd, 0x70, 0x76, 0x5c, 0x21, 0x99, - 0x47, 0x0a, 0x23, 0xa8, 0x53, 0x30, 0xc1, 0xd0, 0x44, 0xa6, 0x52, 0x37, 0x61, 0x52, 0x94, 0x45, - 0xc7, 0xdf, 0x83, 0x34, 0x83, 0x19, 0x8d, 0xcf, 0xa5, 0x3e, 0xe3, 0xc3, 0x5d, 0x26, 0x13, 0xdb, - 0xc2, 0x4f, 0xd1, 0x0a, 0x97, 0x53, 0x2f, 0x43, 0x81, 0x55, 0x0d, 0xf5, 0x3c, 0x7f, 0x92, 0x82, - 0xbc, 0x66, 0xec, 0x84, 0x4c, 0x07, 0x9b, 0x88, 0xe0, 0x53, 0xcf, 0xb6, 0xea, 0x46, 0xc4, 0x99, - 0x5a, 0x99, 0x3c, 0x3e, 0xaa, 0xe4, 0x35, 0x4e, 0xdd, 0x58, 0xd3, 0xf2, 0x82, 0x61, 0xc3, 0x24, - 0xff, 0x0f, 0x60, 0xd7, 0xf0, 0x4d, 0xf4, 0xde, 0x54, 0x18, 0x71, 0xa6, 0xca, 0x5d, 0x6c, 0xf5, - 0xb6, 0xe1, 0x9b, 0xa8, 0x34, 0xea, 0xfd, 0x6e, 0x44, 0x60, 0xee, 0xc5, 0xa6, 0x86, 0x89, 0x36, - 0x4b, 0x69, 0xf8, 0xcd, 0xe6, 0x04, 0x57, 0x93, 0x42, 0x78, 0xbc, 0xc0, 0x56, 0xbf, 0xe1, 0x79, - 0xb6, 0x45, 0xcd, 0x52, 0x1a, 0x99, 0xa3, 0x22, 0x79, 0x04, 0x39, 0xcf, 0x77, 0x1b, 0x38, 0x7c, - 0x19, 0xb4, 0xd1, 0xd2, 0x80, 0x39, 0x1c, 0xf7, 0xb0, 0xba, 0x29, 0x84, 0xd6, 0x9d, 0xd0, 0x6f, - 0x0b, 0x68, 0xb1, 0x26, 0xf2, 0x1a, 0x4c, 0x33, 0x34, 0x7a, 0xe8, 0x1b, 0x4e, 0xb0, 0x43, 0x7d, - 0x4a, 0x71, 0x25, 0xa7, 0xb4, 0x29, 0x46, 0x7e, 0x14, 0x53, 0xcb, 0xdf, 0x52, 0x20, 0x17, 0xa9, - 0x62, 0xd8, 0x9b, 0x46, 0x58, 0xdf, 0xe5, 0x06, 0xd3, 0x78, 0x81, 0xf5, 0xd2, 0xa1, 0x87, 0xdc, - 0x6d, 0xa5, 0x34, 0xfc, 0xee, 0xf4, 0x72, 0x5c, 0xee, 0xe5, 0x3c, 0x64, 0x3c, 0xa3, 0x15, 0x50, - 0x13, 0x3b, 0x9f, 0xd3, 0x44, 0x89, 0x5c, 0x81, 0xa2, 0x47, 0x1d, 0xd3, 0x72, 0x1a, 0x7a, 0xe0, - 0x18, 0x5e, 0xb0, 0xeb, 0x86, 0xc2, 0x0c, 0xd3, 0x82, 0xbe, 0x25, 0xc8, 0xe5, 0xaf, 0xc3, 0x64, - 0xa2, 0x67, 0xa4, 0x08, 0xe3, 0xcc, 0x0f, 0x71, 0x44, 0xec, 0x93, 0xac, 0x42, 0xfa, 0xc0, 0xb0, - 0x5b, 0xd1, 0x40, 0xbd, 0x71, 0x22, 0x73, 0x69, 0x5c, 0xf6, 0xc6, 0xd8, 0x75, 0x45, 0xfd, 0xd6, - 0x18, 0x4c, 0x6a, 0x86, 0xd3, 0xa0, 0x9b, 0xbe, 0xbb, 0x6d, 0xd3, 0x66, 0x40, 0x2e, 0x42, 0xa1, - 0xe5, 0x18, 0x07, 0x86, 0x65, 0x1b, 0xdb, 0x36, 0xdf, 0xae, 0x72, 0x9a, 0x4c, 0x22, 0xef, 0xc0, - 0x39, 0x66, 0x41, 0xea, 0xeb, 0x8e, 0x1b, 0xea, 0x7c, 0xd3, 0xdf, 0x75, 0x6d, 0x93, 0xfa, 0xc2, - 0x09, 0xcc, 0xf1, 0xea, 0xfb, 0x6e, 0x78, 0x8f, 0x55, 0xde, 0xc6, 0x3a, 0xf2, 0x0a, 0x4c, 0x39, - 0xae, 0xce, 0x66, 0x94, 0xce, 0xeb, 0xd1, 0x70, 0x39, 0x6d, 0xc2, 0x71, 0x19, 0xc6, 0x7b, 0x48, - 0x23, 0x8b, 0x30, 0xdd, 0x72, 0x4c, 0xea, 0x8b, 0x99, 0x19, 0xc6, 0x86, 0xec, 0x26, 0x93, 0xf3, - 0x90, 0x73, 0x5c, 0xde, 0x3c, 0x5a, 0x32, 0xa7, 0x65, 0x1d, 0x17, 0x1b, 0x24, 0xd7, 0xa1, 0xb4, - 0xdf, 0xb2, 0x68, 0x50, 0xa7, 0x4e, 0xa8, 0xd3, 0xfd, 0x96, 0x61, 0x07, 0x7a, 0x68, 0xd5, 0xf7, - 0x2c, 0xa7, 0x81, 0x5e, 0x3f, 0xa7, 0xcd, 0xc7, 0xf5, 0xeb, 0x58, 0xfd, 0x88, 0xd7, 0xaa, 0x7b, - 0x30, 0x8d, 0xe6, 0x60, 0x16, 0xb3, 0x30, 0x48, 0x62, 0x0e, 0x7d, 0xbf, 0x45, 0x7d, 0x8b, 0x06, - 0xba, 0x47, 0x7d, 0x3d, 0xa0, 0x75, 0xd7, 0xe1, 0xeb, 0x49, 0xd1, 0x8a, 0xa2, 0x66, 0x93, 0xfa, - 0x5b, 0x48, 0x27, 0x57, 0x61, 0xe6, 0x67, 0x7c, 0x2b, 0x4c, 0x32, 0x8f, 0x21, 0xf3, 0x34, 0xaf, - 0x88, 0x79, 0xd5, 0xdb, 0x00, 0x9b, 0x3e, 0x0d, 0xc3, 0xf6, 0x96, 0x67, 0x38, 0x6c, 0x57, 0x09, - 0x42, 0xc3, 0x0f, 0xf5, 0x68, 0xac, 0xf3, 0x5a, 0x0e, 0x09, 0x6c, 0xcb, 0x39, 0x07, 0x59, 0xea, - 0xe0, 0x86, 0x22, 0xb6, 0xce, 0x0c, 0x75, 0xd8, 0x2e, 0x72, 0x23, 0xf5, 0x9f, 0xbf, 0x57, 0x51, - 0xd4, 0x1f, 0x28, 0x30, 0xbb, 0xea, 0x36, 0x9b, 0x86, 0x63, 0x3e, 0x6c, 0xd1, 0x16, 0xfd, 0x80, - 0x86, 0x3e, 0xc3, 0xfe, 0x2a, 0x4c, 0x61, 0xa3, 0x7a, 0x9d, 0x57, 0x06, 0xa8, 0x78, 0x5c, 0x9b, - 0x44, 0xaa, 0x90, 0x08, 0x58, 0x20, 0xc1, 0xfc, 0x76, 0x87, 0x6b, 0x0c, 0xb9, 0x26, 0x18, 0x31, - 0x66, 0xba, 0x0a, 0x33, 0x4d, 0xe3, 0x50, 0x77, 0x0f, 0xa8, 0x6f, 0x1b, 0x5e, 0xa0, 0x07, 0x94, - 0x3a, 0x62, 0xab, 0x9e, 0x6e, 0x1a, 0x87, 0x0f, 0x04, 0x7d, 0x8b, 0x52, 0xec, 0x4b, 0xe8, 0x53, - 0xaa, 0x07, 0xd6, 0x37, 0xb8, 0x17, 0x48, 0x6b, 0x39, 0x46, 0xd8, 0xb2, 0xbe, 0x41, 0xd5, 0xff, - 0xce, 0x32, 0x37, 0xe5, 0x34, 0x28, 0x73, 0x9f, 0xe4, 0x3d, 0x48, 0x05, 0x9e, 0xe1, 0x20, 0xb0, - 0xc2, 0xd2, 0xab, 0x03, 0x66, 0x72, 0xc7, 0x56, 0x62, 0xad, 0xa3, 0x20, 0xd9, 0x00, 0xc0, 0x49, - 0x25, 0x7b, 0xae, 0x57, 0x46, 0x59, 0x10, 0x91, 0x33, 0xf3, 0x63, 0x97, 0xb9, 0x26, 0x3b, 0xae, - 0xc2, 0xd2, 0xa2, 0xac, 0x85, 0xc7, 0xaf, 0x55, 0x29, 0x8e, 0xad, 0xc6, 0x9d, 0x88, 0xdc, 0x35, - 0x77, 0x01, 0x4d, 0x98, 0x0a, 0xdc, 0x96, 0x5f, 0xa7, 0x7a, 0xe4, 0xa6, 0xd3, 0xb8, 0xdf, 0xdd, - 0x3a, 0x3e, 0xaa, 0x4c, 0x6c, 0x61, 0xcd, 0xe9, 0x76, 0xbd, 0x89, 0xa0, 0xa3, 0xc4, 0x24, 0xfb, - 0x30, 0x2d, 0x9a, 0x63, 0xd8, 0xb0, 0xbd, 0x0c, 0xb6, 0xb7, 0x71, 0x7c, 0x54, 0x99, 0xe4, 0xed, - 0x6d, 0xb1, 0x1a, 0x6c, 0xf0, 0xed, 0x13, 0x35, 0x28, 0xe4, 0xb4, 0xc9, 0x40, 0x52, 0x63, 0xf6, - 0x06, 0x9e, 0xd9, 0x3e, 0x81, 0xe7, 0x2a, 0x4c, 0x0a, 0xdf, 0x60, 0x31, 0x60, 0x6d, 0x8c, 0x94, - 0x0a, 0x4b, 0x25, 0xc9, 0xa8, 0x51, 0x33, 0xb8, 0x6a, 0xa3, 0x48, 0x04, 0x85, 0x6e, 0x73, 0x19, - 0x72, 0x07, 0xb7, 0x06, 0xf4, 0x4c, 0xa5, 0x7c, 0xef, 0xa0, 0xf4, 0x0c, 0xad, 0xe4, 0xc9, 0xa4, - 0x0d, 0x81, 0x7b, 0xb6, 0x9b, 0x7c, 0x74, 0x83, 0x12, 0xa0, 0xa2, 0xab, 0xc3, 0x14, 0x75, 0x7c, - 0x80, 0x3c, 0xbe, 0x01, 0x79, 0x04, 0x85, 0x7a, 0xd3, 0xd4, 0xf7, 0x75, 0xdb, 0xad, 0x1b, 0x76, - 0xa9, 0x80, 0xda, 0xaa, 0x83, 0xa2, 0xae, 0xde, 0x95, 0x19, 0xcd, 0xbd, 0x7a, 0xd3, 0x7c, 0x78, - 0x8f, 0xa9, 0x21, 0x1f, 0xc2, 0x04, 0xd7, 0xda, 0xb0, 0xdd, 0x6d, 0xc3, 0x2e, 0x4d, 0x9c, 0x42, - 0x2d, 0x30, 0xb5, 0xb7, 0x50, 0x0f, 0xb9, 0x05, 0x13, 0xf2, 0xb9, 0xac, 0x34, 0xd9, 0x13, 0x1f, - 0x45, 0x53, 0x1b, 0x47, 0x21, 0x11, 0x7f, 0x14, 0xec, 0x0e, 0x89, 0xc5, 0xd8, 0xb1, 0xd3, 0x2c, - 0x4d, 0xa1, 0x17, 0xed, 0x10, 0xd8, 0xee, 0x1e, 0x79, 0xd8, 0x69, 0xee, 0x8c, 0x45, 0x51, 0xfd, - 0x55, 0x45, 0x6c, 0x31, 0xc3, 0x83, 0x49, 0x03, 0xf2, 0x3e, 0xe3, 0xd4, 0x2d, 0xf4, 0x41, 0xe3, - 0x8b, 0xe3, 0x2b, 0x6b, 0xc7, 0x47, 0x95, 0x1c, 0x5f, 0x68, 0x6b, 0xc1, 0x89, 0xe7, 0xaf, 0x10, - 0xd4, 0x72, 0xa8, 0x76, 0xc3, 0x0c, 0xd4, 0x47, 0x30, 0x15, 0x81, 0x11, 0xe1, 0xd9, 0x0a, 0x64, - 0xb0, 0x36, 0x8a, 0xcf, 0x5e, 0x19, 0x36, 0x2f, 0xa4, 0x15, 0x2f, 0x24, 0xd5, 0x45, 0x98, 0xbc, - 0x85, 0xe7, 0xf4, 0xa1, 0x31, 0xda, 0xf7, 0xc6, 0x60, 0x7a, 0xd9, 0x66, 0x33, 0x27, 0x74, 0xfd, - 0x35, 0xbf, 0xad, 0xb5, 0x1c, 0xf2, 0x11, 0xe4, 0xa2, 0x6e, 0x73, 0xff, 0xbc, 0xb2, 0x7a, 0x7c, - 0x54, 0xc9, 0x0a, 0xf0, 0xcf, 0xdd, 0xe9, 0xac, 0xe8, 0x34, 0xb9, 0x0d, 0x19, 0x7a, 0x40, 0x9d, - 0x90, 0xdb, 0xb4, 0xb0, 0xf4, 0xe6, 0x80, 0x1e, 0x76, 0x61, 0xab, 0xae, 0x33, 0x41, 0x4d, 0xc8, - 0x97, 0x7f, 0x1a, 0xd2, 0x48, 0x20, 0xd7, 0x21, 0xc5, 0xce, 0xc9, 0xc2, 0x6b, 0x97, 0xab, 0xfc, - 0x10, 0x5d, 0x8d, 0x0e, 0xd1, 0xd5, 0x47, 0xd1, 0x21, 0x7a, 0x25, 0xc7, 0x0c, 0xf5, 0x9d, 0xcf, - 0x2b, 0x8a, 0x86, 0x12, 0x6c, 0xa2, 0x24, 0x8f, 0xab, 0x51, 0x51, 0x5d, 0x82, 0xb3, 0x71, 0xeb, - 0xd8, 0x87, 0xc8, 0x98, 0xe7, 0xbb, 0xed, 0x13, 0x77, 0x4d, 0xfd, 0x0b, 0x05, 0xe6, 0xbb, 0x85, - 0xfa, 0x9f, 0x37, 0xc6, 0xbf, 0xc8, 0xf3, 0xc6, 0x2a, 0x64, 0x4d, 0xbf, 0xad, 0xfb, 0x2d, 0x47, - 0x6c, 0x38, 0x57, 0x47, 0x37, 0xa9, 0x96, 0x31, 0xf1, 0x57, 0xfd, 0xb6, 0x02, 0xc5, 0x0e, 0xf6, - 0xff, 0x03, 0x6b, 0xe3, 0x09, 0xcc, 0x48, 0x78, 0x84, 0x19, 0xd7, 0x21, 0x27, 0xba, 0x1a, 0x2d, - 0x90, 0x93, 0xf4, 0x35, 0xcb, 0xfb, 0x1a, 0xa8, 0x2a, 0x4c, 0xdc, 0xd9, 0x7a, 0x70, 0x3f, 0x56, - 0x1b, 0xa5, 0x25, 0x94, 0x4e, 0x5a, 0x42, 0xfd, 0xbe, 0x02, 0x85, 0x7b, 0x6e, 0x63, 0xa4, 0x43, - 0xa7, 0x4d, 0x0f, 0xa8, 0x2d, 0x66, 0x10, 0x2f, 0xb0, 0x53, 0x3b, 0x0f, 0xa0, 0x70, 0x66, 0xf2, - 0xa8, 0x9c, 0x87, 0x54, 0x6c, 0x36, 0xb2, 0x59, 0xc4, 0x42, 0x28, 0xac, 0xe4, 0x07, 0x13, 0x16, - 0x52, 0x61, 0x55, 0x11, 0xc6, 0x9b, 0xc6, 0x21, 0x6e, 0xd3, 0x79, 0x8d, 0x7d, 0xb2, 0x59, 0xea, - 0x19, 0x61, 0x48, 0x7d, 0x47, 0xa4, 0x09, 0xa2, 0xa2, 0xfa, 0x00, 0xc8, 0x3d, 0xb7, 0xc1, 0x02, - 0x73, 0x4b, 0x72, 0x22, 0xff, 0x9f, 0xc5, 0x67, 0x48, 0x12, 0x46, 0x3a, 0xdf, 0x7d, 0x00, 0xb5, - 0xdd, 0x46, 0x55, 0x3e, 0xa8, 0x44, 0xfc, 0x6a, 0x15, 0x66, 0xef, 0xb9, 0x8d, 0x9b, 0x96, 0x4d, - 0x83, 0x7b, 0x56, 0x10, 0x0e, 0xf5, 0x20, 0x9b, 0x30, 0x97, 0xe4, 0x17, 0x10, 0xae, 0x43, 0x7a, - 0x87, 0x11, 0x05, 0x80, 0x0b, 0xfd, 0x00, 0x30, 0x29, 0x39, 0x60, 0x41, 0x01, 0xf5, 0x5d, 0x98, - 0x12, 0x1a, 0x87, 0x5a, 0x9e, 0x40, 0x8a, 0xc9, 0x08, 0xc3, 0xe3, 0x37, 0x73, 0x7e, 0x5b, 0xa1, - 0x51, 0xdf, 0x1b, 0x9e, 0x1a, 0xfb, 0x59, 0x98, 0xda, 0xf4, 0xdd, 0x9d, 0x51, 0x1a, 0x5a, 0x11, - 0xe9, 0xb2, 0x34, 0xa6, 0xcb, 0xaa, 0x03, 0xc3, 0x42, 0x59, 0x63, 0xb5, 0x93, 0x1f, 0x53, 0x8b, - 0x90, 0xc2, 0x8c, 0x4a, 0x0e, 0x52, 0xb7, 0xd7, 0x97, 0x37, 0x8b, 0x67, 0xd4, 0x2b, 0x30, 0x25, - 0x76, 0xca, 0xa1, 0x58, 0xff, 0x08, 0xb7, 0xad, 0x9d, 0x10, 0x97, 0x09, 0x5b, 0xfe, 0x2f, 0x34, - 0x81, 0xf1, 0x3e, 0xa4, 0x71, 0x19, 0x8e, 0x14, 0xbf, 0x76, 0x45, 0x9d, 0x28, 0xa8, 0x5e, 0x65, - 0x1b, 0x9b, 0x80, 0xbb, 0xce, 0xe2, 0x30, 0xd9, 0xd3, 0x2a, 0x49, 0x4f, 0xfb, 0xc9, 0x18, 0x3b, - 0xe6, 0x08, 0x66, 0xb1, 0xbd, 0xbf, 0xe8, 0x4d, 0xe8, 0x16, 0x64, 0x30, 0x3c, 0x8c, 0x36, 0xa1, - 0x2b, 0x43, 0x42, 0xf4, 0x4e, 0x47, 0xa2, 0xbd, 0x96, 0x8b, 0xb3, 0x20, 0x9d, 0xa7, 0x53, 0xc6, - 0x51, 0xcf, 0xe2, 0x28, 0x7a, 0x98, 0xb5, 0x93, 0x39, 0x95, 0x16, 0x14, 0x59, 0xed, 0x1a, 0xdd, - 0x6e, 0x35, 0xa2, 0xb9, 0x90, 0x70, 0xb1, 0xca, 0x0b, 0x71, 0xb1, 0xff, 0x38, 0x06, 0x33, 0x52, - 0xbb, 0x62, 0xe9, 0x7e, 0x5b, 0xe9, 0x8a, 0x41, 0xae, 0x0f, 0xe9, 0x54, 0x42, 0x9c, 0x37, 0x23, - 0xb2, 0x20, 0x3f, 0xc9, 0x3a, 0xf9, 0xe9, 0xe7, 0xcf, 0x09, 0x54, 0xa0, 0xf8, 0xc2, 0x06, 0xab, - 0x4c, 0xa1, 0x20, 0xa1, 0x93, 0x33, 0x19, 0xe3, 0x3c, 0x93, 0xf1, 0x7e, 0x32, 0x93, 0x71, 0x75, - 0x94, 0x86, 0xf8, 0x8c, 0x95, 0xd3, 0x18, 0xbf, 0x30, 0x06, 0x85, 0xe5, 0x7a, 0x68, 0x1d, 0xd0, - 0x87, 0x2d, 0xea, 0xb7, 0xc9, 0x3c, 0x8c, 0x45, 0x0b, 0x7a, 0x25, 0x73, 0x7c, 0x54, 0x19, 0xdb, - 0x58, 0xd3, 0xc6, 0x2c, 0x93, 0xb5, 0x1f, 0xec, 0x47, 0xdb, 0x06, 0xfb, 0x24, 0x37, 0xf0, 0x50, - 0xe0, 0x87, 0x22, 0xe9, 0x37, 0x5a, 0x24, 0xc3, 0x45, 0xd8, 0xe9, 0xda, 0x0a, 0x74, 0xd3, 0x0a, - 0x42, 0xdf, 0xda, 0x6e, 0x75, 0x52, 0x15, 0x93, 0x56, 0xb0, 0xd6, 0x21, 0x92, 0x15, 0x48, 0x7b, - 0xbb, 0x51, 0x96, 0x62, 0x6a, 0xe9, 0xda, 0xa0, 0xed, 0xb3, 0xd3, 0x87, 0xea, 0x26, 0x93, 0xd1, - 0xb8, 0xa8, 0xfa, 0x2a, 0xa4, 0xb1, 0x4c, 0x26, 0x21, 0xbf, 0xa9, 0xad, 0x6f, 0x2e, 0x6b, 0x1b, - 0xf7, 0x6f, 0x15, 0xcf, 0xb0, 0xe2, 0xfa, 0x4f, 0xad, 0xaf, 0x3e, 0x7e, 0xc4, 0x8a, 0x8a, 0xfa, - 0x16, 0xcc, 0xb2, 0x3d, 0x61, 0x8b, 0x06, 0x81, 0xe5, 0x3a, 0xb1, 0x93, 0x2b, 0x43, 0xae, 0x15, - 0x50, 0xdf, 0x31, 0x9a, 0x91, 0x2b, 0x88, 0xcb, 0xea, 0xdf, 0xa7, 0x20, 0x2b, 0xf8, 0x5f, 0xa8, - 0x87, 0x93, 0x31, 0x8c, 0x25, 0x31, 0x30, 0x43, 0xd6, 0x6d, 0x8b, 0x3a, 0xa1, 0x1e, 0x65, 0x71, - 0xf9, 0xee, 0x3d, 0xc9, 0xa9, 0xcb, 0x22, 0x4b, 0x7b, 0x05, 0x8a, 0x98, 0x32, 0xac, 0xe3, 0x05, - 0x8e, 0x8e, 0xaa, 0xf8, 0x4e, 0x3e, 0x2d, 0xd1, 0xef, 0x33, 0x8d, 0x5b, 0x30, 0x65, 0xa0, 0x2d, - 0x75, 0x91, 0xa1, 0xc1, 0x5b, 0x81, 0xc2, 0xd2, 0xe5, 0xd1, 0x8c, 0x2f, 0x66, 0xf1, 0xa4, 0x11, - 0x93, 0x2c, 0x1a, 0x74, 0xe6, 0x4a, 0xe6, 0xe4, 0x73, 0xe5, 0x23, 0xc8, 0xef, 0x1d, 0xe8, 0xe1, - 0xa1, 0xc3, 0x8c, 0xcb, 0x8e, 0xcb, 0x13, 0x2b, 0x2b, 0xff, 0x32, 0xaa, 0x49, 0xf9, 0x7d, 0x58, - 0xcb, 0x32, 0xab, 0x8f, 0x1f, 0x6f, 0x30, 0x97, 0x94, 0xbd, 0x7b, 0xf0, 0xe8, 0xd0, 0x61, 0xee, - 0x75, 0x0f, 0x3f, 0x30, 0xef, 0x64, 0x1b, 0x41, 0xa8, 0x4b, 0xbd, 0x66, 0x27, 0x6e, 0x34, 0x0e, - 0xab, 0xe8, 0x5d, 0x1d, 0x79, 0x04, 0x21, 0xaf, 0x8e, 0x0a, 0x14, 0x0c, 0x16, 0xbf, 0xe9, 0xdb, - 0xed, 0x90, 0xf2, 0x63, 0xf2, 0xb8, 0x06, 0x48, 0x5a, 0x61, 0x14, 0x72, 0x19, 0xa6, 0x9b, 0xc6, - 0xa1, 0x2e, 0x33, 0x15, 0x78, 0x3e, 0xa9, 0x69, 0x1c, 0x2e, 0xc7, 0x7c, 0xea, 0x2f, 0x2b, 0x30, - 0x23, 0xcf, 0x43, 0xbe, 0x1f, 0xbd, 0xc8, 0xd9, 0xf5, 0xec, 0x53, 0xc5, 0x1f, 0x2a, 0x30, 0x97, - 0x5c, 0x13, 0xc2, 0xe9, 0xae, 0x41, 0x2e, 0x10, 0x34, 0xe1, 0x75, 0xd5, 0x01, 0x93, 0x43, 0x88, - 0x47, 0x49, 0x85, 0x48, 0x92, 0xdc, 0xe9, 0xf2, 0x94, 0x83, 0x56, 0x77, 0x8f, 0x49, 0x92, 0xce, - 0x52, 0xdd, 0x07, 0xb2, 0x6a, 0x38, 0x75, 0x6a, 0xe3, 0x30, 0x0d, 0x0d, 0x91, 0x2e, 0x43, 0x0e, - 0x87, 0x99, 0xd5, 0x60, 0xa7, 0x57, 0x0a, 0x6c, 0x6a, 0xa0, 0x30, 0x9b, 0x1a, 0x58, 0xd9, 0xb5, - 0xf2, 0xc6, 0xbb, 0x56, 0xff, 0x2d, 0x98, 0x4d, 0x34, 0x29, 0x6c, 0x53, 0x86, 0x5c, 0x1d, 0xc9, - 0xd4, 0x14, 0x19, 0xe0, 0xb8, 0xcc, 0x82, 0x6f, 0xc4, 0x1b, 0x05, 0xdf, 0x58, 0x50, 0xdb, 0x30, - 0xc7, 0x15, 0x89, 0x0e, 0x0e, 0x45, 0x7f, 0x0d, 0x40, 0x18, 0x31, 0xc2, 0x3f, 0xc1, 0xaf, 0x27, - 0x84, 0x82, 0x8d, 0x35, 0x2d, 0x2f, 0x18, 0x86, 0xf4, 0x61, 0x03, 0xce, 0x76, 0x35, 0xfd, 0xdc, - 0xbd, 0xf8, 0x57, 0x05, 0x8a, 0x5b, 0x9e, 0xe1, 0xb0, 0x1d, 0x26, 0xf6, 0x9e, 0x97, 0xba, 0xba, - 0xb0, 0x02, 0x9d, 0x79, 0x1b, 0x77, 0x47, 0x93, 0xb3, 0xb7, 0xbc, 0x37, 0xef, 0xfc, 0xe8, 0xa8, - 0xf2, 0xd6, 0xc9, 0xb6, 0xe1, 0xbb, 0xb4, 0x2d, 0x25, 0x7d, 0xef, 0x77, 0x92, 0xbe, 0xe3, 0xa7, - 0xd1, 0x28, 0x72, 0xc5, 0xea, 0x5f, 0x29, 0x30, 0x23, 0xf5, 0x4e, 0x58, 0x69, 0x0b, 0x0a, 0xa1, - 0x1b, 0x1a, 0x36, 0x7f, 0x4f, 0x20, 0x4e, 0xf4, 0xd7, 0xfa, 0xe4, 0x87, 0xf8, 0xdd, 0x7f, 0x35, - 0x7a, 0x02, 0x50, 0xfd, 0xe0, 0xc3, 0xd5, 0x55, 0x54, 0x15, 0x65, 0x9d, 0x50, 0x0d, 0x52, 0x98, - 0x2b, 0xe1, 0xa1, 0x54, 0xdd, 0x6d, 0x39, 0xfc, 0xde, 0x24, 0xad, 0x01, 0x92, 0x56, 0x19, 0x85, - 0xbc, 0x0d, 0xf3, 0x86, 0xe7, 0xf9, 0xee, 0xa1, 0xd5, 0x34, 0x42, 0xca, 0x36, 0xd1, 0x3d, 0xe1, - 0x51, 0xf8, 0x4d, 0xd2, 0x9c, 0x54, 0xbb, 0x66, 0x05, 0x7b, 0xdc, 0xb1, 0xfc, 0x04, 0xcc, 0x89, - 0xf4, 0x5e, 0x32, 0xa3, 0x34, 0xca, 0x10, 0xa9, 0xff, 0x03, 0x70, 0xb6, 0x4b, 0xba, 0x37, 0x55, - 0x90, 0xfb, 0xa2, 0x3d, 0xd3, 0xdf, 0x28, 0x30, 0x1b, 0xa5, 0x20, 0xf5, 0xed, 0x76, 0x9c, 0x13, - 0xce, 0xa3, 0xbb, 0xb8, 0x39, 0xf8, 0x60, 0xd3, 0x8b, 0xb5, 0x1a, 0xa7, 0x37, 0xdb, 0x3c, 0xff, - 0xcb, 0xc3, 0xbe, 0x07, 0x6c, 0x04, 0x8e, 0x8f, 0x2a, 0xc5, 0xae, 0xea, 0xb5, 0x4f, 0x3f, 0x7f, - 0x3e, 0xf8, 0x45, 0xaf, 0xab, 0x9d, 0xf2, 0x0f, 0xb2, 0xfc, 0xa2, 0x33, 0xbe, 0x29, 0xea, 0xc9, - 0x02, 0x2b, 0x7d, 0xb2, 0xc0, 0x3f, 0xaf, 0xc0, 0x59, 0xe9, 0xf2, 0x48, 0xef, 0xce, 0x61, 0x3c, - 0x38, 0x3e, 0xaa, 0xcc, 0x3e, 0xee, 0x30, 0x9c, 0x3a, 0xd6, 0x9e, 0x6d, 0x75, 0x2b, 0x33, 0x03, - 0xf2, 0xc7, 0x0a, 0x5c, 0x96, 0x6e, 0x9e, 0x7a, 0x2e, 0xae, 0x24, 0x58, 0xe3, 0x08, 0xeb, 0x6b, - 0xc7, 0x47, 0x95, 0x8b, 0x9d, 0x6b, 0xa9, 0xe4, 0x55, 0xd6, 0xa9, 0x31, 0x5e, 0xf4, 0x07, 0x6a, - 0x36, 0x03, 0xf2, 0x4b, 0x0a, 0x94, 0x92, 0xb7, 0x65, 0x12, 0xc4, 0x14, 0x42, 0xdc, 0x3c, 0x3e, - 0xaa, 0xcc, 0xdd, 0x97, 0xee, 0xce, 0x4e, 0x0d, 0x6b, 0xce, 0xe9, 0xd1, 0x66, 0x06, 0xe4, 0x10, - 0x48, 0x74, 0xcf, 0x26, 0x61, 0x48, 0x23, 0x86, 0xbb, 0xc7, 0x47, 0x95, 0xe9, 0xfb, 0xfc, 0xd6, - 0xed, 0xd4, 0xcd, 0x4f, 0x3b, 0xb2, 0x22, 0x33, 0x20, 0xbf, 0xa6, 0xc0, 0xf9, 0xae, 0x5b, 0x3f, - 0x09, 0x41, 0x06, 0x11, 0x6c, 0x1d, 0x1f, 0x55, 0xce, 0x3d, 0x4e, 0x32, 0x9d, 0x1a, 0xc9, 0xb9, - 0x56, 0x3f, 0x85, 0x66, 0x40, 0x7e, 0x5f, 0x01, 0xf5, 0x59, 0x37, 0x8b, 0x12, 0xb4, 0x2c, 0x42, - 0x7b, 0x72, 0x7c, 0x54, 0x59, 0x78, 0xd8, 0xf7, 0x9e, 0xf1, 0xd4, 0x08, 0x17, 0xf6, 0x07, 0xe8, - 0x35, 0x83, 0xf2, 0xa7, 0x4a, 0xec, 0xeb, 0x92, 0x9e, 0x42, 0x3e, 0x82, 0xa5, 0xf9, 0x11, 0x6c, - 0x2b, 0x79, 0x04, 0x7b, 0xf7, 0xc4, 0x2e, 0x49, 0xf6, 0x0a, 0xd2, 0xa9, 0xec, 0x4e, 0x2a, 0xa7, - 0x14, 0x73, 0xea, 0x15, 0x98, 0x18, 0x35, 0x9b, 0xfb, 0x07, 0x69, 0x71, 0x55, 0xf0, 0x63, 0x79, - 0x34, 0x22, 0x67, 0x3c, 0xc6, 0x5e, 0x40, 0xc6, 0xe3, 0xaf, 0x15, 0x98, 0xf3, 0x45, 0x47, 0x12, - 0xae, 0x9f, 0x27, 0x2e, 0xde, 0x1b, 0x96, 0xe3, 0xe9, 0x9c, 0xef, 0x23, 0x25, 0x49, 0x9f, 0xbf, - 0x29, 0x7c, 0xfe, 0x4c, 0x77, 0xfd, 0x73, 0x3b, 0xfd, 0x19, 0xbf, 0xbb, 0xa5, 0xf2, 0x77, 0x15, - 0xee, 0xf5, 0xe5, 0xa0, 0x2a, 0xe2, 0x8a, 0x82, 0xaa, 0xa8, 0x3c, 0xda, 0x83, 0xb4, 0xf7, 0x21, - 0x6d, 0x39, 0x3b, 0x6e, 0x94, 0xbf, 0x39, 0x51, 0xaa, 0x0b, 0x05, 0xcb, 0x1f, 0xc3, 0x7c, 0x7f, - 0x93, 0xf4, 0x99, 0xdc, 0x77, 0x93, 0x93, 0xfb, 0x9d, 0x91, 0x8d, 0x2e, 0x77, 0x3a, 0x39, 0xa9, - 0x53, 0xc5, 0xb4, 0xfa, 0x66, 0xf2, 0xbe, 0x7d, 0x84, 0xb9, 0xed, 0xc0, 0x5c, 0x52, 0x42, 0xd8, - 0xec, 0x43, 0xc8, 0xc5, 0x0f, 0x42, 0x78, 0xec, 0xf5, 0xf6, 0x90, 0x6b, 0x67, 0x59, 0x4d, 0x10, - 0xbd, 0x1a, 0x89, 0x0f, 0x26, 0xa2, 0xac, 0xbe, 0x01, 0x64, 0xad, 0xf3, 0xd4, 0x73, 0x68, 0xba, - 0x13, 0x93, 0xb8, 0xae, 0x3f, 0xc2, 0xfb, 0xc6, 0x3f, 0x1d, 0x83, 0x09, 0x64, 0x8d, 0x5e, 0x36, - 0x7e, 0x04, 0xb9, 0xf8, 0xe6, 0x99, 0x2f, 0x52, 0x5c, 0x47, 0xa7, 0xbd, 0x73, 0xce, 0x06, 0xe2, - 0xb6, 0xf9, 0x75, 0x98, 0xa1, 0x4e, 0xdd, 0x6f, 0x7b, 0x78, 0xea, 0x17, 0xd7, 0x98, 0x18, 0x62, - 0x6b, 0xc5, 0x4e, 0x85, 0x48, 0x63, 0x56, 0xa2, 0x68, 0x96, 0xe7, 0xc2, 0x79, 0x30, 0xc9, 0x23, - 0x53, 0x4c, 0x99, 0x77, 0x18, 0x78, 0xb4, 0x99, 0x92, 0x18, 0xf8, 0x21, 0x77, 0x11, 0x8a, 0xe2, - 0x10, 0xbd, 0x47, 0xdb, 0x42, 0x0d, 0x7f, 0xa9, 0x23, 0x52, 0x0a, 0x77, 0x69, 0x9b, 0xab, 0x4a, - 0x72, 0x72, 0x7d, 0x99, 0x2e, 0x4e, 0x1e, 0xb7, 0x7e, 0x15, 0xa6, 0x22, 0xeb, 0xc6, 0xd7, 0x2a, - 0x19, 0xec, 0x5f, 0x74, 0xf6, 0x7c, 0x6d, 0xd0, 0xd9, 0x53, 0xb2, 0x76, 0x74, 0x64, 0xe4, 0xc2, - 0xea, 0x75, 0x98, 0xc1, 0xa7, 0x0b, 0x4d, 0xea, 0x9c, 0xec, 0xc0, 0xa2, 0xfe, 0x30, 0x05, 0x44, - 0x16, 0x15, 0xb8, 0x3c, 0xbc, 0x44, 0x11, 0x54, 0x81, 0xed, 0xce, 0x40, 0x6c, 0xdd, 0x2a, 0xaa, - 0xab, 0xae, 0x6d, 0xd3, 0x7a, 0x48, 0xcd, 0xb8, 0xae, 0xe7, 0x26, 0x5d, 0x6a, 0x83, 0xac, 0x02, - 0x60, 0xe6, 0xc2, 0xa7, 0x01, 0x3d, 0x59, 0x1a, 0x2e, 0xcf, 0xe4, 0x34, 0x26, 0x56, 0xfe, 0x27, - 0x05, 0xe6, 0xfb, 0x34, 0xc7, 0x4e, 0x51, 0x17, 0xf0, 0x64, 0xc6, 0x6b, 0xc4, 0x54, 0xee, 0x10, - 0x98, 0xc7, 0x30, 0x3c, 0x2f, 0xca, 0x08, 0x1a, 0x9e, 0x47, 0x4a, 0x90, 0x35, 0xd9, 0x41, 0xfd, - 0xe1, 0x3d, 0xf1, 0x40, 0x29, 0x2a, 0x92, 0x79, 0xc8, 0xec, 0x18, 0x96, 0xdd, 0x79, 0xdb, 0xc5, - 0x4b, 0xf2, 0x26, 0x95, 0xfe, 0x82, 0x37, 0xa9, 0xf2, 0x5f, 0x2a, 0x70, 0x61, 0x90, 0x41, 0xc9, - 0xd7, 0x3a, 0x2e, 0xaf, 0xb0, 0xb4, 0x76, 0xb2, 0x91, 0xea, 0x6f, 0x31, 0x31, 0x46, 0xe8, 0x3e, - 0xbf, 0x12, 0xbd, 0x99, 0xe0, 0xee, 0x33, 0x91, 0x21, 0xd9, 0xb7, 0xab, 0xcf, 0x1e, 0x61, 0x2e, - 0xb6, 0xf4, 0xe7, 0x17, 0x20, 0x23, 0x56, 0xe6, 0xf7, 0x14, 0x98, 0x90, 0x9f, 0xb1, 0x92, 0xea, - 0x68, 0x0f, 0x55, 0xa3, 0x69, 0x5d, 0xae, 0x8d, 0xcc, 0xcf, 0xbb, 0xa7, 0xbe, 0xf6, 0xe9, 0x3f, - 0xfc, 0xc7, 0x6f, 0x8c, 0xbd, 0x4c, 0x2a, 0x35, 0xe1, 0x35, 0x6a, 0xf2, 0x2b, 0xd7, 0xda, 0xc7, - 0x62, 0xd0, 0x9e, 0xb2, 0x40, 0x33, 0x1b, 0x79, 0xb3, 0x41, 0xa9, 0xee, 0xe4, 0xa3, 0xd8, 0xf2, - 0xd5, 0x51, 0x58, 0x05, 0x96, 0x37, 0x10, 0xcb, 0x6b, 0xa4, 0x1c, 0x63, 0x31, 0x39, 0x47, 0x07, - 0xc6, 0x93, 0x3c, 0xc9, 0xd6, 0x76, 0xa9, 0x61, 0x87, 0xbb, 0xc4, 0x87, 0x34, 0x3e, 0x22, 0x25, - 0x83, 0xfc, 0x82, 0xfc, 0xec, 0xb4, 0xbc, 0x38, 0x9c, 0x51, 0x40, 0x99, 0x47, 0x28, 0x45, 0x32, - 0x15, 0x43, 0xc1, 0x2b, 0x11, 0xd2, 0x82, 0x14, 0xde, 0x73, 0x5d, 0x1e, 0xa2, 0x29, 0x6a, 0x71, - 0x94, 0x87, 0xac, 0xea, 0x45, 0x6c, 0xac, 0x4c, 0x4a, 0xc9, 0xc6, 0x24, 0xe3, 0x3f, 0xe5, 0x8f, - 0x56, 0xf1, 0x4a, 0x83, 0xbc, 0x3e, 0xda, 0xc5, 0x07, 0x07, 0x70, 0xed, 0x24, 0xb7, 0x24, 0xea, - 0x59, 0x44, 0x32, 0x4d, 0x26, 0x63, 0x24, 0xec, 0x74, 0x45, 0x3e, 0x51, 0x20, 0xc3, 0xe3, 0x59, - 0x32, 0xf4, 0x69, 0x51, 0x6c, 0xec, 0x2b, 0x23, 0x70, 0x8a, 0x66, 0x5f, 0xc6, 0x66, 0x5f, 0x22, - 0xe7, 0xa5, 0x66, 0x19, 0x83, 0x64, 0x81, 0x00, 0x32, 0xfc, 0xf5, 0xc8, 0x40, 0x04, 0x89, 0x07, - 0x26, 0x65, 0xf9, 0x7a, 0x57, 0xfc, 0x89, 0x08, 0x0b, 0x94, 0x84, 0xd5, 0x7b, 0x1b, 0x15, 0x7f, - 0x4d, 0xd2, 0x69, 0xf4, 0xbb, 0x0a, 0xe4, 0xe3, 0xdb, 0xfa, 0x81, 0x76, 0xef, 0x7e, 0xa3, 0x30, - 0xd0, 0xee, 0x3d, 0x0f, 0x08, 0xd4, 0x2b, 0x88, 0xe5, 0x12, 0x79, 0x39, 0xc6, 0x62, 0x44, 0x3c, - 0x38, 0x17, 0x24, 0x4c, 0xdf, 0x57, 0x60, 0x2a, 0xf9, 0x9a, 0x83, 0x8c, 0xf4, 0x56, 0x45, 0x3e, - 0x5f, 0x94, 0xdf, 0x3a, 0x81, 0x84, 0x80, 0xf8, 0x3a, 0x42, 0x7c, 0x95, 0x5c, 0xea, 0x03, 0x11, - 0x47, 0xab, 0xf6, 0x71, 0x14, 0xd6, 0x3d, 0x25, 0xbf, 0xa2, 0xc0, 0x84, 0x9c, 0xc9, 0x1d, 0xe8, - 0xc7, 0xfa, 0xdc, 0xc6, 0x0c, 0xf4, 0x63, 0xfd, 0x32, 0xd5, 0xea, 0x79, 0x84, 0x37, 0x4b, 0x66, - 0x62, 0x78, 0x71, 0xfa, 0xf9, 0xb7, 0x44, 0xa6, 0x1d, 0xdf, 0x90, 0xfd, 0xf8, 0x10, 0x55, 0x10, - 0xd1, 0x79, 0x72, 0x2e, 0x46, 0x84, 0x6f, 0xe1, 0x74, 0x19, 0x57, 0x41, 0x4a, 0x2c, 0x93, 0x81, - 0x7f, 0x94, 0xd0, 0x93, 0xf3, 0x2e, 0x57, 0x47, 0x65, 0x7f, 0xb6, 0xa7, 0x47, 0x2e, 0x7e, 0x0f, - 0x22, 0xcd, 0xb0, 0xdf, 0x55, 0x60, 0x32, 0x91, 0x2c, 0x26, 0xb5, 0xa1, 0x4d, 0x25, 0x33, 0xda, - 0xe5, 0x37, 0x47, 0x17, 0x78, 0xe6, 0x0a, 0x10, 0xe8, 0x84, 0xb9, 0x24, 0x7c, 0x9f, 0x28, 0x90, - 0x8f, 0x53, 0xb4, 0x03, 0x57, 0x65, 0x77, 0x9a, 0x7a, 0xe0, 0xaa, 0xec, 0xc9, 0xfa, 0xaa, 0x25, - 0xc4, 0x44, 0xd4, 0x8e, 0x37, 0x0c, 0x3c, 0xc3, 0xb9, 0xa1, 0x5c, 0x25, 0xdf, 0xc4, 0x1d, 0xbb, - 0xbe, 0x37, 0xd8, 0x1f, 0x26, 0x5e, 0x7c, 0x94, 0x07, 0xed, 0x52, 0xf2, 0xb3, 0x9f, 0x3e, 0x8e, - 0x29, 0x40, 0x45, 0x92, 0x09, 0x7e, 0x4e, 0x81, 0xac, 0x78, 0xd3, 0x31, 0x70, 0x33, 0x4e, 0xbe, - 0xfb, 0x18, 0x1d, 0x82, 0x8a, 0x10, 0x2e, 0x48, 0x3b, 0xb1, 0xc7, 0x35, 0x75, 0x61, 0x88, 0xde, - 0x50, 0x0f, 0xc2, 0x90, 0x7c, 0x4c, 0x72, 0x1a, 0x0c, 0x4d, 0xae, 0x49, 0xc2, 0xf0, 0x9b, 0xcc, - 0xcf, 0x48, 0x0f, 0x7d, 0x06, 0xaf, 0xea, 0xde, 0x17, 0x44, 0x83, 0x57, 0x75, 0x9f, 0x17, 0x44, - 0xea, 0x25, 0x44, 0xf5, 0x25, 0xf2, 0x92, 0xb4, 0xaa, 0x1b, 0x78, 0x00, 0xea, 0x8a, 0x95, 0x84, - 0xf4, 0x40, 0xd3, 0x24, 0x5f, 0x14, 0x95, 0xdf, 0x18, 0xcc, 0xda, 0xf5, 0x9e, 0x4a, 0xbd, 0x8a, - 0x50, 0x5e, 0x21, 0xea, 0x00, 0x28, 0xb5, 0x8f, 0x19, 0xe1, 0x29, 0xf9, 0x26, 0xa4, 0xee, 0xb9, - 0x8d, 0x60, 0x60, 0xdc, 0x22, 0x3d, 0x2b, 0x3b, 0x29, 0x94, 0x7e, 0xbe, 0xae, 0x21, 0x5b, 0xe4, - 0xd7, 0x15, 0xfc, 0x83, 0x8d, 0x4e, 0x62, 0x6c, 0xa0, 0x4f, 0xe9, 0x77, 0x7f, 0x31, 0xd0, 0xa7, - 0xf4, 0xcd, 0xb9, 0xa9, 0x0b, 0x88, 0xaa, 0x44, 0xe6, 0xe5, 0x59, 0xcc, 0xf8, 0xc4, 0x0b, 0x8e, - 0xa7, 0x90, 0xe6, 0x1b, 0xe8, 0x6b, 0xc3, 0x33, 0x1e, 0xc3, 0x03, 0xc8, 0xe4, 0x76, 0xf9, 0x8c, - 0x90, 0x46, 0xde, 0x24, 0x7f, 0x87, 0x05, 0xfb, 0x52, 0x9e, 0x82, 0x8c, 0xfa, 0x90, 0x79, 0xa4, - 0x60, 0xbf, 0x4f, 0x1e, 0xa5, 0xcf, 0x8c, 0xe9, 0x06, 0x55, 0xab, 0x37, 0xcd, 0x7d, 0x04, 0xf3, - 0xdb, 0x0a, 0x14, 0xa4, 0xe4, 0xc8, 0xc0, 0xdd, 0xa9, 0x37, 0x89, 0xd2, 0x6f, 0xb4, 0x12, 0x7f, - 0x63, 0x2b, 0xc9, 0x68, 0xd4, 0x73, 0xfd, 0x50, 0xbd, 0x8c, 0xe0, 0x2e, 0x92, 0x85, 0x4e, 0xf4, - 0xdf, 0x11, 0x48, 0xba, 0xff, 0x0c, 0x4f, 0x14, 0x0c, 0x71, 0xbe, 0x52, 0xa6, 0x66, 0x60, 0x30, - 0x9a, 0xcc, 0x3a, 0xf4, 0x75, 0xbf, 0x8c, 0x41, 0x82, 0xf0, 0x8b, 0x0a, 0x40, 0xe7, 0xa8, 0x48, - 0xae, 0x8d, 0x78, 0xa2, 0x1c, 0xbe, 0xb4, 0x7a, 0xcf, 0x9f, 0xea, 0x4b, 0x08, 0xe7, 0x2c, 0x99, - 0x95, 0x77, 0x03, 0xc1, 0xb4, 0xa2, 0x7e, 0xf6, 0xef, 0x0b, 0x67, 0x3e, 0x3b, 0x5e, 0x50, 0xfe, - 0xee, 0x78, 0x41, 0xf9, 0xe7, 0xe3, 0x05, 0xe5, 0xdf, 0x8e, 0x17, 0x94, 0xef, 0xfc, 0x70, 0xe1, - 0xcc, 0x93, 0x5c, 0xa4, 0x73, 0x3b, 0x83, 0xf9, 0x81, 0x2f, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xe3, 0x99, 0x74, 0x56, 0x78, 0x3d, 0x00, 0x00, + // 4539 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5b, 0x5d, 0x6c, 0x1c, 0x47, + 0x72, 0xd6, 0x90, 0xfb, 0x5b, 0xcb, 0x9f, 0x65, 0x93, 0xa2, 0x56, 0x6b, 0x1d, 0x57, 0x1e, 0xd9, + 0x32, 0x25, 0xcb, 0xbb, 0x36, 0xcf, 0x0e, 0x14, 0x27, 0xfe, 0xe1, 0x9f, 0x25, 0x5a, 0x32, 0x45, + 0x0d, 0x29, 0x5f, 0xe0, 0x1c, 0x3c, 0x18, 0xee, 0x34, 0x97, 0x73, 0x9c, 0x9d, 0x19, 0xce, 0xcc, + 0x32, 0xdc, 0x33, 0x7c, 0x71, 0x1c, 0x24, 0xc8, 0x0f, 0xee, 0x72, 0x97, 0x3f, 0x1c, 0x10, 0x04, + 0x08, 0xee, 0x21, 0x09, 0x0e, 0x48, 0x90, 0x20, 0x2f, 0x09, 0x10, 0x20, 0x40, 0x1e, 0x02, 0x3f, + 0x26, 0x48, 0x10, 0x04, 0x09, 0x40, 0x27, 0xbc, 0x3c, 0x24, 0x41, 0x1e, 0xf3, 0x74, 0x4f, 0x41, + 0x57, 0xf7, 0xcc, 0xf6, 0xec, 0xae, 0x76, 0x97, 0xa2, 0x75, 0xb8, 0x07, 0x69, 0xa7, 0xab, 0xab, + 0xaa, 0xbf, 0xae, 0xee, 0xae, 0xae, 0xae, 0x6e, 0xc2, 0x95, 0x80, 0xfa, 0x47, 0xd4, 0xaf, 0xf1, + 0x1f, 0x6f, 0xb7, 0x16, 0x84, 0x46, 0xd8, 0x0a, 0xaa, 0x9e, 0xef, 0x86, 0x2e, 0xb9, 0x5c, 0x77, + 0xeb, 0x07, 0xbe, 0x6b, 0xd4, 0xf7, 0xab, 0x9c, 0xa1, 0x1a, 0xf1, 0x95, 0x8b, 0xbb, 0x2d, 0xcb, + 0x36, 0x6b, 0x96, 0xb3, 0xe7, 0x72, 0xe6, 0xf2, 0x6c, 0xc3, 0x0d, 0x02, 0xcb, 0xab, 0xf1, 0x1f, + 0x41, 0xbc, 0x84, 0xd2, 0xde, 0x6e, 0xcd, 0xf0, 0x3c, 0x9d, 0xe9, 0x16, 0xaa, 0xcb, 0x24, 0xaa, + 0x30, 0x8d, 0xd0, 0x10, 0xb4, 0xeb, 0x02, 0x8c, 0x69, 0x19, 0x0d, 0xc7, 0x0d, 0x42, 0xab, 0x1e, + 0x30, 0x86, 0x4e, 0x49, 0xf0, 0x95, 0x23, 0xd0, 0x88, 0x35, 0x01, 0xb9, 0xac, 0x06, 0xa1, 0xeb, + 0x1b, 0x0d, 0x5a, 0xa3, 0x4e, 0xc3, 0x72, 0xa2, 0x1f, 0x6f, 0xb7, 0xd6, 0x3c, 0xaa, 0xd7, 0x63, + 0x79, 0xc1, 0x63, 0x53, 0x23, 0xa0, 0x7a, 0x42, 0xbe, 0x12, 0xd5, 0x89, 0xdf, 0x5d, 0x23, 0xa0, + 0xd8, 0x02, 0x8d, 0x80, 0xb7, 0x42, 0xcb, 0xae, 0xd9, 0x6e, 0x83, 0xfd, 0x8b, 0x14, 0x22, 0xad, + 0xe5, 0xf8, 0x34, 0x70, 0xed, 0x23, 0x6a, 0xea, 0x86, 0x69, 0xfa, 0xa2, 0xee, 0x19, 0x1a, 0xd6, + 0xcd, 0x9a, 0x6f, 0xec, 0x85, 0xf8, 0x9f, 0xb7, 0x8b, 0x3f, 0xa2, 0x72, 0xae, 0xe1, 0x36, 0x5c, + 0xfc, 0xac, 0xb1, 0x2f, 0x41, 0xbd, 0xd2, 0x70, 0xdd, 0x86, 0x4d, 0x6b, 0x86, 0x67, 0xd5, 0x0c, + 0xc7, 0x71, 0x43, 0x23, 0xb4, 0x5c, 0x27, 0x46, 0x28, 0x6a, 0xb1, 0xb4, 0xdb, 0xda, 0xab, 0x85, + 0x56, 0x93, 0x06, 0xa1, 0xd1, 0x14, 0x36, 0x57, 0xab, 0x30, 0xbb, 0x4a, 0xfd, 0xd0, 0xda, 0xb3, + 0xea, 0x46, 0x48, 0x03, 0x8d, 0x1e, 0xb6, 0x68, 0x10, 0x92, 0x4b, 0x90, 0x75, 0x5c, 0x93, 0xea, + 0x96, 0x59, 0x52, 0xae, 0x2a, 0x8b, 0x79, 0x2d, 0xc3, 0x8a, 0x1b, 0xa6, 0xfa, 0x3f, 0x29, 0x20, + 0x92, 0xc0, 0x1a, 0x0d, 0x0d, 0xcb, 0x0e, 0xc8, 0x43, 0x48, 0x85, 0x6d, 0x8f, 0x22, 0xf3, 0xd4, + 0xd2, 0x1b, 0xd5, 0xc7, 0xce, 0x85, 0x6a, 0xaf, 0xb0, 0x4c, 0xda, 0x69, 0x7b, 0x54, 0x43, 0x55, + 0xe4, 0x1a, 0x4c, 0x52, 0xdf, 0x77, 0x7d, 0xbd, 0x49, 0x83, 0xc0, 0x68, 0xd0, 0xd2, 0x18, 0x02, + 0x99, 0x40, 0xe2, 0x7b, 0x9c, 0x46, 0x08, 0xa4, 0xd8, 0x9c, 0x28, 0x8d, 0x5f, 0x55, 0x16, 0x27, + 0x34, 0xfc, 0x26, 0x1a, 0x64, 0xf6, 0x2c, 0x6a, 0x9b, 0x41, 0x29, 0x75, 0x75, 0x7c, 0xb1, 0xb0, + 0xf4, 0xea, 0xd9, 0xd0, 0xbc, 0x83, 0xb2, 0x2b, 0xa9, 0xcf, 0x4e, 0x2a, 0x17, 0x34, 0xa1, 0xa9, + 0xfc, 0x17, 0x63, 0x90, 0xe1, 0x15, 0x64, 0x1e, 0x32, 0x56, 0x10, 0xb4, 0xa8, 0x1f, 0x59, 0x86, + 0x97, 0x48, 0x09, 0xb2, 0x41, 0x6b, 0xf7, 0x6b, 0xb4, 0x1e, 0x0a, 0xa4, 0x51, 0x91, 0x7c, 0x09, + 0xe0, 0xc8, 0xb0, 0x2d, 0x53, 0xdf, 0xf3, 0xdd, 0x26, 0x42, 0x1d, 0xd7, 0xf2, 0x48, 0x79, 0xc7, + 0x77, 0x9b, 0xa4, 0x02, 0x05, 0x5e, 0xdd, 0x72, 0x42, 0xcb, 0x2e, 0xa5, 0xb0, 0x9e, 0x4b, 0x3c, + 0x62, 0x14, 0x72, 0x05, 0xf2, 0x6c, 0x8e, 0xd0, 0x20, 0xa0, 0x41, 0x29, 0x7d, 0x75, 0x7c, 0x31, + 0xaf, 0x75, 0x08, 0xa4, 0x06, 0xb3, 0x81, 0xd5, 0x70, 0x8c, 0xb0, 0xe5, 0x53, 0xdd, 0xb0, 0x1b, + 0xae, 0x6f, 0x85, 0xfb, 0xcd, 0x52, 0x06, 0x31, 0x90, 0xb8, 0x6a, 0x39, 0xaa, 0x61, 0x70, 0xbc, + 0xd6, 0xae, 0x6d, 0xd5, 0xf5, 0x03, 0xda, 0x2e, 0x65, 0x91, 0x2f, 0xcf, 0x29, 0xf7, 0x68, 0x9b, + 0x3c, 0x03, 0xf9, 0x03, 0xda, 0xd6, 0x5b, 0x68, 0xf3, 0x1c, 0xb6, 0x96, 0x3b, 0xa0, 0xed, 0x47, + 0x68, 0xef, 0x5b, 0x40, 0xe8, 0x71, 0x48, 0x1d, 0x93, 0x9a, 0x7a, 0x87, 0x2b, 0x8f, 0x5c, 0xc5, + 0xa8, 0xe6, 0x9e, 0xe0, 0x56, 0xdf, 0x84, 0xe9, 0xae, 0xb1, 0x25, 0x19, 0x18, 0x5b, 0x5d, 0x2e, + 0x5e, 0x20, 0x39, 0x48, 0x6d, 0x3e, 0x58, 0x5b, 0x2f, 0x2a, 0x64, 0x12, 0xf2, 0xab, 0xf7, 0x37, + 0xd6, 0x37, 0x77, 0xf4, 0xd5, 0xe5, 0xe2, 0x18, 0x01, 0xc8, 0xf0, 0x62, 0x71, 0x5c, 0x75, 0x61, + 0x2e, 0x39, 0x39, 0x03, 0xcf, 0x75, 0x02, 0x4a, 0xbe, 0x02, 0x13, 0x75, 0x89, 0x5e, 0x52, 0x70, + 0x9c, 0x5f, 0x3a, 0xd3, 0x38, 0x8b, 0x01, 0x4e, 0x28, 0x52, 0xdf, 0x82, 0x29, 0x51, 0x3d, 0x6c, + 0x21, 0x90, 0x39, 0x48, 0xfb, 0xd4, 0x30, 0xdb, 0x38, 0xd8, 0x39, 0x8d, 0x17, 0xd4, 0xff, 0x56, + 0x60, 0x3a, 0xd6, 0x20, 0xd0, 0x7e, 0x90, 0x54, 0x91, 0x5e, 0x59, 0x3e, 0x3d, 0xa9, 0x64, 0x36, + 0x99, 0x9a, 0xb5, 0x1f, 0x9e, 0x54, 0xbe, 0xdc, 0xb0, 0xc2, 0xfd, 0xd6, 0x6e, 0xb5, 0xee, 0x36, + 0x6b, 0x71, 0x07, 0xcc, 0xdd, 0xce, 0x77, 0xcd, 0x3b, 0x68, 0xd4, 0x84, 0x07, 0xac, 0x72, 0xb1, + 0x18, 0xc5, 0x9b, 0x90, 0x15, 0x33, 0x01, 0x71, 0x14, 0x96, 0x16, 0x24, 0x23, 0x30, 0x47, 0x53, + 0x7d, 0x14, 0x3b, 0x9a, 0x65, 0xd3, 0xf4, 0x45, 0xaf, 0x23, 0x21, 0xf2, 0x3a, 0x00, 0xfa, 0x66, + 0x9d, 0xf9, 0x66, 0x9c, 0x9a, 0x85, 0xa5, 0x8b, 0x92, 0x0a, 0xac, 0xac, 0x6e, 0x38, 0x7b, 0xae, + 0x90, 0xcc, 0x23, 0x85, 0x11, 0xd4, 0x29, 0x98, 0x60, 0x68, 0x22, 0x53, 0xa9, 0x5b, 0x30, 0x29, + 0xca, 0xa2, 0xe3, 0x6f, 0x41, 0x9a, 0xc1, 0x8c, 0xc6, 0xe7, 0x5a, 0x9f, 0xf1, 0xe1, 0xde, 0x94, + 0x89, 0x6d, 0xe3, 0xa7, 0x68, 0x85, 0xcb, 0xa9, 0xd7, 0xa1, 0xc0, 0xaa, 0x86, 0x3a, 0xa5, 0x3f, + 0x4b, 0x41, 0x5e, 0x33, 0xf6, 0x42, 0xa6, 0x83, 0xcd, 0x51, 0xf0, 0xa9, 0x67, 0x5b, 0x75, 0x23, + 0xe2, 0x4c, 0xad, 0x4c, 0x9e, 0x9e, 0x54, 0xf2, 0x1a, 0xa7, 0x6e, 0xac, 0x69, 0x79, 0xc1, 0xb0, + 0x61, 0x92, 0x9f, 0x00, 0xd8, 0x37, 0x7c, 0x13, 0x1d, 0x3b, 0x15, 0x46, 0x9c, 0xa9, 0x72, 0xef, + 0x5b, 0xbd, 0x6b, 0xf8, 0x26, 0x2a, 0x8d, 0x7a, 0xbf, 0x1f, 0x11, 0x98, 0xe7, 0xb1, 0xa9, 0x61, + 0xa2, 0xcd, 0x52, 0x1a, 0x7e, 0xb3, 0x39, 0xc1, 0xd5, 0xa4, 0x10, 0x1e, 0x2f, 0x30, 0xc7, 0x60, + 0x78, 0x9e, 0x6d, 0x51, 0xb3, 0x94, 0x46, 0xe6, 0xa8, 0x48, 0x76, 0x20, 0xe7, 0xf9, 0x6e, 0x03, + 0x87, 0x2f, 0x83, 0x36, 0x5a, 0x1a, 0x30, 0x87, 0xe3, 0x1e, 0x56, 0xb7, 0x84, 0xd0, 0xba, 0x13, + 0xfa, 0x6d, 0x01, 0x2d, 0xd6, 0x44, 0x5e, 0x80, 0x69, 0x86, 0x46, 0x0f, 0x7d, 0xc3, 0x09, 0xf6, + 0xa8, 0x4f, 0x29, 0x2e, 0xf2, 0x94, 0x36, 0xc5, 0xc8, 0x3b, 0x31, 0xb5, 0xfc, 0x4d, 0x05, 0x72, + 0x91, 0x2a, 0x86, 0xbd, 0x69, 0x84, 0xf5, 0x7d, 0x6e, 0x30, 0x8d, 0x17, 0x58, 0x2f, 0x1d, 0x7a, + 0xcc, 0x3d, 0x5a, 0x4a, 0xc3, 0xef, 0x4e, 0x2f, 0xc7, 0xe5, 0x5e, 0xce, 0x43, 0xc6, 0x33, 0x5a, + 0x01, 0x35, 0xb1, 0xf3, 0x39, 0x4d, 0x94, 0xc8, 0x0d, 0x28, 0x7a, 0xd4, 0x31, 0x2d, 0xa7, 0xa1, + 0x07, 0x8e, 0xe1, 0x05, 0xfb, 0x6e, 0x28, 0xcc, 0x30, 0x2d, 0xe8, 0xdb, 0x82, 0x5c, 0xfe, 0x1a, + 0x4c, 0x26, 0x7a, 0x46, 0x8a, 0x30, 0xce, 0x5c, 0x14, 0x47, 0xc4, 0x3e, 0xc9, 0x2a, 0xa4, 0x8f, + 0x0c, 0xbb, 0x15, 0x0d, 0xd4, 0x4b, 0x67, 0x32, 0x97, 0xc6, 0x65, 0x5f, 0x1f, 0xbb, 0xad, 0xa8, + 0xdf, 0x1c, 0x83, 0x49, 0xcd, 0x70, 0x1a, 0x74, 0xcb, 0x77, 0x77, 0x6d, 0xda, 0x0c, 0xc8, 0x55, + 0x28, 0xb4, 0x1c, 0xe3, 0xc8, 0xb0, 0x6c, 0x63, 0xd7, 0xe6, 0x3b, 0x59, 0x4e, 0x93, 0x49, 0xe4, + 0x35, 0xb8, 0xc4, 0x2c, 0x48, 0x7d, 0xdd, 0x71, 0x43, 0x9d, 0xc7, 0x03, 0xfb, 0xae, 0x6d, 0x52, + 0x5f, 0x38, 0x81, 0x39, 0x5e, 0xbd, 0xe9, 0x86, 0xf7, 0x59, 0xe5, 0x5d, 0xac, 0x23, 0xcf, 0xc1, + 0x94, 0xe3, 0xea, 0x6c, 0x46, 0xe9, 0xbc, 0x1e, 0x0d, 0x97, 0xd3, 0x26, 0x1c, 0x97, 0x61, 0xbc, + 0x8f, 0x34, 0xb2, 0x08, 0xd3, 0x2d, 0xc7, 0xa4, 0xbe, 0x98, 0x99, 0x61, 0x6c, 0xc8, 0x6e, 0x32, + 0xb9, 0x0c, 0x39, 0xc7, 0xe5, 0xcd, 0xa3, 0x25, 0x73, 0x5a, 0xd6, 0x71, 0xb1, 0x41, 0x72, 0x1b, + 0x4a, 0x87, 0x2d, 0x8b, 0x06, 0x75, 0xea, 0x84, 0x3a, 0x3d, 0x6c, 0x19, 0x76, 0xa0, 0x87, 0x56, + 0xfd, 0xc0, 0x72, 0x1a, 0xb8, 0x21, 0xe4, 0xb4, 0xf9, 0xb8, 0x7e, 0x1d, 0xab, 0x77, 0x78, 0xad, + 0x7a, 0x00, 0xd3, 0x68, 0x0e, 0x66, 0x31, 0x0b, 0xe3, 0x27, 0xe6, 0xeb, 0x0f, 0x5b, 0xd4, 0xb7, + 0x68, 0xa0, 0x7b, 0xd4, 0xd7, 0x03, 0x5a, 0x77, 0x1d, 0xbe, 0x9e, 0x14, 0xad, 0x28, 0x6a, 0xb6, + 0xa8, 0xbf, 0x8d, 0x74, 0x72, 0x13, 0x66, 0x7e, 0xce, 0xb7, 0xc2, 0x24, 0xf3, 0x18, 0x32, 0x4f, + 0xf3, 0x8a, 0x98, 0x57, 0xbd, 0x0b, 0xb0, 0xe5, 0xd3, 0x30, 0x6c, 0x6f, 0x7b, 0x86, 0xc3, 0x36, + 0x9c, 0x20, 0x34, 0xfc, 0x50, 0x8f, 0xc6, 0x3a, 0xaf, 0xe5, 0x90, 0xc0, 0x76, 0xa3, 0x4b, 0x90, + 0xa5, 0x0e, 0xee, 0x35, 0x62, 0x57, 0xcd, 0x50, 0x87, 0x6d, 0x30, 0xaf, 0xa7, 0xfe, 0xeb, 0x0f, + 0x2a, 0x8a, 0xfa, 0x7d, 0x05, 0x66, 0x57, 0xdd, 0x66, 0xd3, 0x70, 0xcc, 0x87, 0x2d, 0xda, 0xa2, + 0xef, 0xd1, 0xd0, 0x67, 0xd8, 0x9f, 0x87, 0x29, 0x6c, 0x54, 0xaf, 0xf3, 0xca, 0x00, 0x15, 0x8f, + 0x6b, 0x93, 0x48, 0x15, 0x12, 0x01, 0x8b, 0x31, 0x98, 0xdf, 0xee, 0x70, 0x8d, 0x21, 0xd7, 0x04, + 0x23, 0xc6, 0x4c, 0x37, 0x61, 0xa6, 0x69, 0x1c, 0xeb, 0xee, 0x11, 0xf5, 0x6d, 0xc3, 0x0b, 0xf4, + 0x80, 0x52, 0x47, 0xec, 0xe2, 0xd3, 0x4d, 0xe3, 0xf8, 0x81, 0xa0, 0x6f, 0x53, 0x8a, 0x7d, 0x09, + 0x7d, 0x4a, 0xf5, 0xc0, 0xfa, 0x3a, 0xf7, 0x02, 0x69, 0x2d, 0xc7, 0x08, 0xdb, 0xd6, 0xd7, 0xa9, + 0xfa, 0xbf, 0x59, 0xe6, 0xa6, 0x9c, 0x06, 0x65, 0xee, 0x93, 0xbc, 0x05, 0xa9, 0xc0, 0x33, 0x1c, + 0x04, 0x56, 0x58, 0x7a, 0x7e, 0xc0, 0x4c, 0xee, 0xd8, 0x4a, 0xac, 0x75, 0x14, 0x24, 0x1b, 0x00, + 0x38, 0xa9, 0x64, 0xcf, 0xf5, 0xdc, 0x28, 0x0b, 0x22, 0x72, 0x66, 0x7e, 0xec, 0x32, 0xd7, 0x64, + 0xc7, 0x55, 0x58, 0x5a, 0x94, 0xb5, 0xf0, 0xd0, 0xb6, 0x2a, 0x85, 0xb8, 0xd5, 0xb8, 0x13, 0x91, + 0xbb, 0xe6, 0x2e, 0xa0, 0x09, 0x53, 0x81, 0xdb, 0xf2, 0xeb, 0x54, 0x8f, 0xdc, 0x74, 0x1a, 0xf7, + 0xbb, 0x3b, 0xa7, 0x27, 0x95, 0x89, 0x6d, 0xac, 0x39, 0xdf, 0xae, 0x37, 0x11, 0x74, 0x94, 0x98, + 0xe4, 0x10, 0xa6, 0x45, 0x73, 0x0c, 0x1b, 0xb6, 0x97, 0xc1, 0xf6, 0x36, 0x4e, 0x4f, 0x2a, 0x93, + 0xbc, 0xbd, 0x6d, 0x56, 0x83, 0x0d, 0xbe, 0x7a, 0xa6, 0x06, 0x85, 0x9c, 0x36, 0x19, 0x48, 0x6a, + 0xcc, 0xde, 0x98, 0x34, 0xdb, 0x27, 0x26, 0x5d, 0x85, 0x49, 0xe1, 0x1b, 0x2c, 0x06, 0xac, 0x8d, + 0x41, 0x54, 0x61, 0xa9, 0x24, 0x19, 0x35, 0x6a, 0x06, 0x57, 0x6d, 0x14, 0x89, 0xa0, 0xd0, 0x5d, + 0x2e, 0x43, 0xde, 0xc5, 0xad, 0x01, 0x3d, 0x53, 0x29, 0xdf, 0x3b, 0x28, 0x3d, 0x43, 0x2b, 0x79, + 0x32, 0x69, 0x43, 0xe0, 0x9e, 0xed, 0x1d, 0x3e, 0xba, 0x41, 0x09, 0x50, 0xd1, 0xcd, 0x61, 0x8a, + 0x3a, 0x3e, 0x40, 0x1e, 0xdf, 0x80, 0xec, 0x40, 0xa1, 0xde, 0x34, 0xf5, 0x43, 0xdd, 0x76, 0xeb, + 0x86, 0x5d, 0x2a, 0xa0, 0xb6, 0xea, 0xa0, 0xa8, 0xab, 0x77, 0x65, 0x46, 0x73, 0xaf, 0xde, 0x34, + 0x1f, 0xde, 0x67, 0x6a, 0xc8, 0xfb, 0x30, 0xc1, 0xb5, 0x36, 0x6c, 0x77, 0xd7, 0xb0, 0x4b, 0x13, + 0xe7, 0x50, 0x0b, 0x4c, 0xed, 0x1d, 0xd4, 0x43, 0xee, 0xc0, 0x84, 0x7c, 0x64, 0x2b, 0x4d, 0xf6, + 0xc4, 0x47, 0xd1, 0xd4, 0xc6, 0x51, 0x48, 0xc4, 0x1f, 0x05, 0xbb, 0x43, 0x62, 0xe1, 0x77, 0xec, + 0x34, 0x4b, 0x53, 0xe8, 0x45, 0x3b, 0x04, 0xb6, 0xbb, 0x47, 0x1e, 0x76, 0x9a, 0x3b, 0x63, 0x51, + 0x54, 0x7f, 0x5d, 0x11, 0x5b, 0xcc, 0xf0, 0x60, 0xd2, 0x80, 0xbc, 0xcf, 0x38, 0x75, 0x0b, 0x7d, + 0xd0, 0xf8, 0xe2, 0xf8, 0xca, 0xda, 0xe9, 0x49, 0x25, 0xc7, 0x17, 0xda, 0x5a, 0x70, 0xe6, 0xf9, + 0x2b, 0x04, 0xb5, 0x1c, 0xaa, 0xdd, 0x30, 0x03, 0x75, 0x07, 0xa6, 0x22, 0x30, 0x22, 0x3c, 0x5b, + 0x81, 0x0c, 0xd6, 0x46, 0xf1, 0xd9, 0x73, 0xc3, 0xe6, 0x85, 0xb4, 0xe2, 0x85, 0xa4, 0xba, 0x08, + 0x93, 0x77, 0xf0, 0x08, 0x3f, 0x34, 0x46, 0xfb, 0xee, 0x18, 0x4c, 0x2f, 0xdb, 0x6c, 0xe6, 0x84, + 0xae, 0xbf, 0xe6, 0xb7, 0xb5, 0x96, 0x43, 0x3e, 0x84, 0x5c, 0xd4, 0x6d, 0xee, 0x9f, 0x57, 0x56, + 0x4f, 0x4f, 0x2a, 0x59, 0x01, 0xfe, 0x89, 0x3b, 0x9d, 0x15, 0x9d, 0x26, 0x77, 0x21, 0x43, 0x8f, + 0xa8, 0x13, 0x72, 0x9b, 0x16, 0x96, 0x5e, 0x1e, 0xd0, 0xc3, 0x2e, 0x6c, 0xd5, 0x75, 0x26, 0xa8, + 0x09, 0xf9, 0xf2, 0xcf, 0x42, 0x1a, 0x09, 0xe4, 0x36, 0xa4, 0xd8, 0x11, 0x5a, 0x78, 0xed, 0x72, + 0x95, 0x9f, 0xaf, 0xab, 0xd1, 0xf9, 0xba, 0xba, 0x13, 0x9d, 0xaf, 0x57, 0x72, 0xcc, 0x50, 0xdf, + 0xfe, 0xbc, 0xa2, 0x68, 0x28, 0xc1, 0x26, 0x4a, 0xf2, 0x24, 0x1b, 0x15, 0xd5, 0x25, 0xb8, 0x18, + 0xb7, 0x8e, 0x7d, 0x88, 0x8c, 0x79, 0xb9, 0xdb, 0x3e, 0x71, 0xd7, 0xd4, 0xbf, 0x52, 0x60, 0xbe, + 0x5b, 0xa8, 0xff, 0x79, 0x63, 0xfc, 0x8b, 0x3c, 0x6f, 0xac, 0x42, 0xd6, 0xf4, 0xdb, 0xba, 0xdf, + 0x72, 0xc4, 0x86, 0x73, 0x73, 0x74, 0x93, 0x6a, 0x19, 0x13, 0x7f, 0xd5, 0x6f, 0x29, 0x50, 0xec, + 0x60, 0xff, 0x31, 0x58, 0x1b, 0x1f, 0xc0, 0x8c, 0x84, 0x47, 0x98, 0x71, 0x1d, 0x72, 0xa2, 0xab, + 0xd1, 0x02, 0x39, 0x4b, 0x5f, 0xb3, 0xbc, 0xaf, 0x81, 0xaa, 0xc2, 0xc4, 0xbb, 0xdb, 0x0f, 0x36, + 0x63, 0xb5, 0x51, 0xc6, 0x42, 0xe9, 0x64, 0x2c, 0xd4, 0xef, 0x29, 0x50, 0xb8, 0xef, 0x36, 0x46, + 0x3a, 0x74, 0xda, 0xf4, 0x88, 0xda, 0x62, 0x06, 0xf1, 0x02, 0x3b, 0xd0, 0xf3, 0x00, 0x0a, 0x67, + 0x26, 0x8f, 0xca, 0x79, 0x48, 0xc5, 0x66, 0x23, 0x9b, 0x45, 0x2c, 0x84, 0xc2, 0x4a, 0x7e, 0x30, + 0x61, 0x21, 0x15, 0x56, 0x15, 0x61, 0xbc, 0x69, 0x1c, 0xe3, 0x36, 0x9d, 0xd7, 0xd8, 0x27, 0x9b, + 0xa5, 0x9e, 0x11, 0x86, 0xd4, 0x77, 0x44, 0x06, 0x21, 0x2a, 0xaa, 0x0f, 0x80, 0xdc, 0x77, 0x1b, + 0x2c, 0x30, 0xb7, 0x24, 0x27, 0xf2, 0x93, 0x2c, 0x3e, 0x43, 0x92, 0x30, 0xd2, 0xe5, 0xee, 0x03, + 0xa8, 0xed, 0x36, 0xaa, 0xf2, 0x41, 0x25, 0xe2, 0x57, 0xab, 0x30, 0x7b, 0xdf, 0x6d, 0xbc, 0x63, + 0xd9, 0x34, 0xb8, 0x6f, 0x05, 0xe1, 0x50, 0x0f, 0xb2, 0x05, 0x73, 0x49, 0x7e, 0x01, 0xe1, 0x36, + 0xa4, 0xf7, 0x18, 0x51, 0x00, 0xb8, 0xd2, 0x0f, 0x00, 0x93, 0x92, 0x03, 0x16, 0x14, 0x50, 0xdf, + 0x80, 0x29, 0xa1, 0x71, 0xa8, 0xe5, 0x09, 0xa4, 0x98, 0x8c, 0x30, 0x3c, 0x7e, 0x33, 0xe7, 0xb7, + 0x1d, 0x1a, 0xf5, 0x83, 0xe1, 0x59, 0xb3, 0x9f, 0x87, 0xa9, 0x2d, 0xdf, 0xdd, 0x1b, 0xa5, 0xa1, + 0x15, 0x91, 0x49, 0x4b, 0x63, 0x26, 0xad, 0x3a, 0x30, 0x2c, 0x94, 0x35, 0x56, 0x3b, 0xa9, 0x33, + 0xb5, 0x08, 0x29, 0x4c, 0xb6, 0xe4, 0x20, 0x75, 0x77, 0x7d, 0x79, 0xab, 0x78, 0x41, 0xbd, 0x01, + 0x53, 0x62, 0xa7, 0x1c, 0x8a, 0xf5, 0x4f, 0x70, 0xdb, 0xda, 0x0b, 0x71, 0x99, 0xb0, 0xe5, 0xff, + 0x54, 0x13, 0x18, 0x6f, 0x43, 0x1a, 0x97, 0xe1, 0x48, 0xf1, 0x6b, 0x57, 0xd4, 0x89, 0x82, 0xea, + 0x4d, 0xb6, 0xb1, 0x09, 0xb8, 0xeb, 0x2c, 0x0e, 0x93, 0x3d, 0xad, 0x92, 0xf4, 0xb4, 0x9f, 0x8c, + 0xb1, 0x63, 0x8e, 0x60, 0x16, 0xdb, 0xfb, 0xd3, 0xde, 0x84, 0xee, 0x40, 0x06, 0xc3, 0xc3, 0x68, + 0x13, 0xba, 0x31, 0x24, 0x44, 0xef, 0x74, 0x24, 0xda, 0x6b, 0xb9, 0x38, 0x0b, 0xd2, 0x79, 0x3a, + 0x65, 0x1c, 0xf5, 0x2c, 0x8e, 0xa2, 0x87, 0x59, 0x3b, 0x99, 0x53, 0x69, 0x41, 0x91, 0xd5, 0xae, + 0xd1, 0xdd, 0x56, 0x23, 0x9a, 0x0b, 0x09, 0x17, 0xab, 0x3c, 0x15, 0x17, 0xfb, 0x4f, 0x63, 0x30, + 0x23, 0xb5, 0x2b, 0x96, 0xee, 0xb7, 0x94, 0xae, 0x18, 0xe4, 0xf6, 0x90, 0x4e, 0x25, 0xc4, 0x79, + 0x33, 0x22, 0x0b, 0xf2, 0xd3, 0xac, 0x93, 0x9f, 0x7e, 0xfe, 0x84, 0x40, 0x05, 0x8a, 0x2f, 0x6c, + 0xb0, 0xca, 0x14, 0x0a, 0x12, 0x3a, 0x39, 0x93, 0x31, 0xce, 0x33, 0x19, 0x6f, 0x27, 0x33, 0x19, + 0x37, 0x47, 0x69, 0x88, 0xcf, 0x58, 0x39, 0x8d, 0xf1, 0x4b, 0x63, 0x50, 0x58, 0xae, 0x87, 0xd6, + 0x11, 0x7d, 0xd8, 0xa2, 0x7e, 0x9b, 0xcc, 0xc3, 0x58, 0xb4, 0xa0, 0x57, 0x32, 0xa7, 0x27, 0x95, + 0xb1, 0x8d, 0x35, 0x6d, 0xcc, 0x32, 0x59, 0xfb, 0xc1, 0x61, 0xb4, 0x6d, 0xb0, 0x4f, 0xf2, 0x3a, + 0x1e, 0x0a, 0xfc, 0x50, 0x24, 0xfd, 0x46, 0x8b, 0x64, 0xb8, 0x08, 0x3b, 0x5d, 0x5b, 0x81, 0x6e, + 0x5a, 0x41, 0xe8, 0x5b, 0xbb, 0xad, 0x4e, 0xaa, 0x62, 0xd2, 0x0a, 0xd6, 0x3a, 0x44, 0xb2, 0x02, + 0x69, 0x6f, 0x3f, 0xca, 0x52, 0x4c, 0x2d, 0xdd, 0x1a, 0xb4, 0x7d, 0x76, 0xfa, 0x50, 0xdd, 0x62, + 0x32, 0x1a, 0x17, 0x55, 0x9f, 0x87, 0x34, 0x96, 0xc9, 0x24, 0xe4, 0xb7, 0xb4, 0xf5, 0xad, 0x65, + 0x6d, 0x63, 0xf3, 0x4e, 0xf1, 0x02, 0x2b, 0xae, 0xff, 0xcc, 0xfa, 0xea, 0xa3, 0x1d, 0x56, 0x54, + 0xd4, 0x57, 0x60, 0x96, 0xed, 0x09, 0xdb, 0x34, 0x08, 0x2c, 0xd7, 0x89, 0x9d, 0x5c, 0x19, 0x72, + 0xad, 0x80, 0xfa, 0x8e, 0xd1, 0x8c, 0x5c, 0x41, 0x5c, 0x56, 0xff, 0x21, 0x05, 0x59, 0xc1, 0xff, + 0x54, 0x3d, 0x9c, 0x8c, 0x61, 0x2c, 0x89, 0x81, 0x19, 0xb2, 0x6e, 0x5b, 0xd4, 0x09, 0xf5, 0x28, + 0x8b, 0xcb, 0x77, 0xef, 0x49, 0x4e, 0x5d, 0x16, 0x59, 0xda, 0x1b, 0x50, 0xc4, 0x94, 0x61, 0x1d, + 0xef, 0x76, 0x74, 0x54, 0xc5, 0x77, 0xf2, 0x69, 0x89, 0xbe, 0xc9, 0x34, 0x6e, 0xc3, 0x94, 0x81, + 0xb6, 0xd4, 0x45, 0x86, 0x06, 0x2f, 0x0c, 0x0a, 0x4b, 0xd7, 0x47, 0x33, 0xbe, 0x98, 0xc5, 0x93, + 0x46, 0x4c, 0xb2, 0x68, 0xd0, 0x99, 0x2b, 0x99, 0xb3, 0xcf, 0x95, 0x0f, 0x21, 0x7f, 0x70, 0xa4, + 0x87, 0xc7, 0x0e, 0x33, 0x2e, 0x3b, 0x2e, 0x4f, 0xac, 0xac, 0xfc, 0xeb, 0xa8, 0x26, 0xe5, 0x57, + 0x65, 0x2d, 0xcb, 0xac, 0x3e, 0x7a, 0xb4, 0xc1, 0x5c, 0x52, 0xf6, 0xde, 0xd1, 0xce, 0xb1, 0xc3, + 0xdc, 0xeb, 0x01, 0x7e, 0x60, 0xde, 0xc9, 0x36, 0x82, 0x50, 0x97, 0x7a, 0xcd, 0x4e, 0xdc, 0x68, + 0x1c, 0x56, 0xd1, 0xbb, 0x3a, 0xf2, 0x08, 0x42, 0x5e, 0x1d, 0x15, 0x28, 0x18, 0x2c, 0x7e, 0xd3, + 0x77, 0xdb, 0x21, 0xe5, 0xc7, 0xe4, 0x71, 0x0d, 0x90, 0xb4, 0xc2, 0x28, 0xe4, 0x3a, 0x4c, 0x37, + 0x8d, 0x63, 0x5d, 0x66, 0x2a, 0xf0, 0x7c, 0x52, 0xd3, 0x38, 0x5e, 0x8e, 0xf9, 0xd4, 0x5f, 0x55, + 0x60, 0x46, 0x9e, 0x87, 0x7c, 0x3f, 0x7a, 0x9a, 0xb3, 0xeb, 0xf1, 0xa7, 0x8a, 0x3f, 0x56, 0x60, + 0x2e, 0xb9, 0x26, 0x84, 0xd3, 0x5d, 0x83, 0x5c, 0x20, 0x68, 0xc2, 0xeb, 0xaa, 0x03, 0x26, 0x87, + 0x10, 0x8f, 0x92, 0x0a, 0x91, 0x24, 0x79, 0xb7, 0xcb, 0x53, 0x0e, 0x5a, 0xdd, 0x3d, 0x26, 0x49, + 0x3a, 0x4b, 0xf5, 0x10, 0xc8, 0xaa, 0xe1, 0xd4, 0xa9, 0x8d, 0xc3, 0x34, 0x34, 0x44, 0xba, 0x0e, + 0x39, 0x1c, 0x66, 0x56, 0x83, 0x9d, 0x5e, 0x29, 0xb0, 0xa9, 0x81, 0xc2, 0x6c, 0x6a, 0x60, 0x65, + 0xd7, 0xca, 0x1b, 0xef, 0x5a, 0xfd, 0x77, 0x60, 0x36, 0xd1, 0xa4, 0xb0, 0x4d, 0x19, 0x72, 0x75, + 0x24, 0x53, 0x53, 0x64, 0x80, 0xe3, 0x32, 0x0b, 0xbe, 0x11, 0x6f, 0x14, 0x7c, 0x63, 0x41, 0x6d, + 0xc3, 0x1c, 0x57, 0x24, 0x3a, 0x38, 0x14, 0xfd, 0x2d, 0x00, 0x61, 0xc4, 0x08, 0xff, 0x04, 0xbf, + 0x9e, 0x10, 0x0a, 0x36, 0xd6, 0xb4, 0xbc, 0x60, 0x18, 0xd2, 0x87, 0x0d, 0xb8, 0xd8, 0xd5, 0xf4, + 0x13, 0xf7, 0xe2, 0xdf, 0x14, 0x28, 0x6e, 0x7b, 0x86, 0xc3, 0x76, 0x98, 0xd8, 0x7b, 0x5e, 0xeb, + 0xea, 0xc2, 0x0a, 0x74, 0xe6, 0x6d, 0xdc, 0x1d, 0x4d, 0xce, 0xde, 0xf2, 0xde, 0xbc, 0xf6, 0xc3, + 0x93, 0xca, 0x2b, 0x67, 0xdb, 0x86, 0xef, 0xd1, 0xb6, 0x94, 0xf4, 0xdd, 0xec, 0x24, 0x7d, 0xc7, + 0xcf, 0xa3, 0x51, 0xe4, 0x8a, 0xd5, 0xbf, 0x51, 0x60, 0x46, 0xea, 0x9d, 0xb0, 0xd2, 0x36, 0x14, + 0x42, 0x37, 0x34, 0x6c, 0xfe, 0xd4, 0x40, 0x9c, 0xe8, 0x6f, 0xf5, 0xc9, 0x0f, 0xf1, 0x67, 0x01, + 0xd5, 0xe8, 0x75, 0x40, 0xf5, 0xbd, 0xf7, 0x57, 0x57, 0x51, 0x55, 0x94, 0x75, 0x42, 0x35, 0x48, + 0x61, 0xae, 0x84, 0x87, 0x52, 0x75, 0xb7, 0xe5, 0xf0, 0x7b, 0x93, 0xb4, 0x06, 0x48, 0x5a, 0x65, + 0x14, 0xf2, 0x2a, 0xcc, 0x1b, 0x9e, 0xe7, 0xbb, 0xc7, 0x56, 0xd3, 0x08, 0x29, 0xdb, 0x44, 0x0f, + 0x84, 0x47, 0xe1, 0x37, 0x49, 0x73, 0x52, 0xed, 0x9a, 0x15, 0x1c, 0x70, 0xc7, 0xf2, 0x53, 0x30, + 0x27, 0xd2, 0x7b, 0xc9, 0x8c, 0xd2, 0x28, 0x43, 0xa4, 0xfe, 0x1f, 0xc0, 0xc5, 0x2e, 0xe9, 0xde, + 0x54, 0x41, 0xee, 0x8b, 0xf6, 0x4c, 0x7f, 0xa7, 0xc0, 0x6c, 0x94, 0x82, 0xd4, 0x77, 0xdb, 0x71, + 0x4e, 0x38, 0x8f, 0xee, 0xe2, 0x9d, 0xc1, 0x07, 0x9b, 0x5e, 0xac, 0xd5, 0x38, 0xbd, 0xd9, 0xe6, + 0xf9, 0x5f, 0x1e, 0xf6, 0x3d, 0x60, 0x23, 0x70, 0x7a, 0x52, 0x29, 0x76, 0x55, 0xaf, 0x7d, 0xfa, + 0xf9, 0x93, 0xc1, 0x2f, 0x7a, 0x5d, 0xed, 0x94, 0xbf, 0x9f, 0xe5, 0x17, 0x9d, 0xf1, 0x4d, 0x51, + 0x4f, 0x16, 0x58, 0xe9, 0x93, 0x05, 0xfe, 0x45, 0x05, 0x2e, 0x4a, 0x97, 0x47, 0x7a, 0x77, 0x0e, + 0xe3, 0xc1, 0xe9, 0x49, 0x65, 0xf6, 0x51, 0x87, 0xe1, 0xdc, 0xb1, 0xf6, 0x6c, 0xab, 0x5b, 0x99, + 0x19, 0x90, 0x3f, 0x55, 0xe0, 0xba, 0x74, 0xf3, 0xd4, 0x73, 0x71, 0x25, 0xc1, 0x1a, 0x47, 0x58, + 0x5f, 0x3d, 0x3d, 0xa9, 0x5c, 0xed, 0x5c, 0x4b, 0x25, 0xaf, 0xb2, 0xce, 0x8d, 0xf1, 0xaa, 0x3f, + 0x50, 0xb3, 0x19, 0x90, 0x5f, 0x51, 0xa0, 0x94, 0xbc, 0x2d, 0x93, 0x20, 0xa6, 0x10, 0xe2, 0xd6, + 0xe9, 0x49, 0x65, 0x6e, 0x53, 0xba, 0x3b, 0x3b, 0x37, 0xac, 0x39, 0xa7, 0x47, 0x9b, 0x19, 0x90, + 0x63, 0x20, 0xd1, 0x3d, 0x9b, 0x84, 0x21, 0x8d, 0x18, 0xee, 0x9d, 0x9e, 0x54, 0xa6, 0x37, 0xf9, + 0xad, 0xdb, 0xb9, 0x9b, 0x9f, 0x76, 0x64, 0x45, 0x66, 0x40, 0x7e, 0x43, 0x81, 0xcb, 0x5d, 0xb7, + 0x7e, 0x12, 0x82, 0x0c, 0x22, 0xd8, 0x3e, 0x3d, 0xa9, 0x5c, 0x7a, 0x94, 0x64, 0x3a, 0x37, 0x92, + 0x4b, 0xad, 0x7e, 0x0a, 0xcd, 0x80, 0xfc, 0xa1, 0x02, 0xea, 0xe3, 0x6e, 0x16, 0x25, 0x68, 0x59, + 0x84, 0xf6, 0xc1, 0xe9, 0x49, 0x65, 0xe1, 0x61, 0xdf, 0x7b, 0xc6, 0x73, 0x23, 0x5c, 0x38, 0x1c, + 0xa0, 0xd7, 0x0c, 0xca, 0x9f, 0x2a, 0xb1, 0xaf, 0x4b, 0x7a, 0x0a, 0xf9, 0x08, 0x96, 0xe6, 0x47, + 0xb0, 0xed, 0xe4, 0x11, 0xec, 0x8d, 0x33, 0xbb, 0x24, 0xd9, 0x2b, 0x48, 0xa7, 0xb2, 0x77, 0x53, + 0x39, 0xa5, 0x98, 0x53, 0x6f, 0xc0, 0xc4, 0xa8, 0xd9, 0xdc, 0x3f, 0x4a, 0x8b, 0xab, 0x82, 0x1f, + 0xc9, 0xa3, 0x11, 0x39, 0xe3, 0x31, 0xf6, 0x14, 0x32, 0x1e, 0x7f, 0xab, 0xc0, 0x9c, 0x2f, 0x3a, + 0x92, 0x70, 0xfd, 0x3c, 0x71, 0xf1, 0xd6, 0xb0, 0x1c, 0x4f, 0xe7, 0x7c, 0x1f, 0x29, 0x49, 0xfa, + 0xfc, 0x2d, 0xe1, 0xf3, 0x67, 0xba, 0xeb, 0x9f, 0xd8, 0xe9, 0xcf, 0xf8, 0xdd, 0x2d, 0x95, 0xbf, + 0xa3, 0x70, 0xaf, 0x2f, 0x07, 0x55, 0x11, 0x57, 0x14, 0x54, 0x45, 0xe5, 0xd1, 0xde, 0xaa, 0xbd, + 0x0d, 0x69, 0xcb, 0xd9, 0x73, 0xa3, 0xfc, 0xcd, 0x99, 0x52, 0x5d, 0x28, 0x58, 0xfe, 0x08, 0xe6, + 0xfb, 0x9b, 0xa4, 0xcf, 0xe4, 0xbe, 0x97, 0x9c, 0xdc, 0xaf, 0x8d, 0x6c, 0x74, 0xb9, 0xd3, 0xc9, + 0x49, 0x9d, 0x2a, 0xa6, 0xd5, 0x97, 0x93, 0xf7, 0xed, 0x23, 0xcc, 0x6d, 0x07, 0xe6, 0x92, 0x12, + 0xc2, 0x66, 0xef, 0x43, 0x2e, 0x7e, 0x10, 0xc2, 0x63, 0xaf, 0x57, 0x87, 0x5c, 0x3b, 0xcb, 0x6a, + 0x82, 0xe8, 0xd5, 0x48, 0x7c, 0x30, 0x11, 0x65, 0xf5, 0x25, 0x20, 0x6b, 0x9d, 0x57, 0xa0, 0x43, + 0xd3, 0x9d, 0x98, 0xc4, 0x75, 0xfd, 0x11, 0x9e, 0x3e, 0xfe, 0xf9, 0x18, 0x4c, 0x20, 0x6b, 0xf4, + 0xe8, 0xf1, 0x43, 0xc8, 0xc5, 0x37, 0xcf, 0x7c, 0x91, 0xe2, 0x3a, 0x3a, 0xef, 0x9d, 0x73, 0x36, + 0x10, 0xb7, 0xcd, 0x2f, 0xc2, 0x0c, 0x75, 0xea, 0x7e, 0xdb, 0xc3, 0x53, 0xbf, 0xb8, 0xc6, 0xc4, + 0x10, 0x5b, 0x2b, 0x76, 0x2a, 0x44, 0x1a, 0xb3, 0x12, 0x45, 0xb3, 0x3c, 0x17, 0xce, 0x83, 0x49, + 0x1e, 0x99, 0x62, 0xca, 0xbc, 0xc3, 0xc0, 0xa3, 0xcd, 0x94, 0xc4, 0xc0, 0x0f, 0xb9, 0x8b, 0x50, + 0x14, 0x87, 0xe8, 0x03, 0xda, 0x16, 0x6a, 0xf8, 0x4b, 0x1d, 0x91, 0x52, 0xb8, 0x47, 0xdb, 0x5c, + 0x55, 0x92, 0x93, 0xeb, 0xcb, 0x74, 0x71, 0xf2, 0xb8, 0xf5, 0x2b, 0x30, 0x15, 0x59, 0x37, 0xbe, + 0x56, 0xc9, 0x60, 0xff, 0xa2, 0xb3, 0xe7, 0x0b, 0x83, 0xce, 0x9e, 0x92, 0xb5, 0xa3, 0x23, 0x23, + 0x17, 0x56, 0x6f, 0xc3, 0x0c, 0x3e, 0x5d, 0x68, 0x52, 0xe7, 0x6c, 0x07, 0x16, 0xf5, 0x07, 0x29, + 0x20, 0xb2, 0xa8, 0xc0, 0xe5, 0xe1, 0x25, 0x8a, 0xa0, 0x0a, 0x6c, 0xef, 0x0e, 0xc4, 0xd6, 0xad, + 0xa2, 0xba, 0xea, 0xda, 0x36, 0xad, 0x87, 0xd4, 0x8c, 0xeb, 0x7a, 0x6e, 0xd2, 0xa5, 0x36, 0xc8, + 0x2a, 0x00, 0x66, 0x2e, 0x7c, 0x1a, 0xd0, 0xb3, 0xa5, 0xe1, 0xf2, 0x4c, 0x4e, 0x63, 0x62, 0xe5, + 0x7f, 0x56, 0x60, 0xbe, 0x4f, 0x73, 0xec, 0x14, 0x75, 0x05, 0x4f, 0x66, 0xbc, 0x46, 0x4c, 0xe5, + 0x0e, 0x81, 0x79, 0x0c, 0xc3, 0xf3, 0xa2, 0x8c, 0xa0, 0xe1, 0x79, 0xa4, 0x04, 0x59, 0x93, 0x1d, + 0xd4, 0x1f, 0xde, 0x17, 0x0f, 0x94, 0xa2, 0x22, 0x99, 0x87, 0xcc, 0x9e, 0x61, 0xd9, 0x9d, 0xb7, + 0x5d, 0xbc, 0x24, 0x6f, 0x52, 0xe9, 0x2f, 0x78, 0x93, 0x2a, 0xff, 0xb5, 0x02, 0x57, 0x06, 0x19, + 0x94, 0x7c, 0xb5, 0xe3, 0xf2, 0x0a, 0x4b, 0x6b, 0x67, 0x1b, 0xa9, 0xfe, 0x16, 0x13, 0x63, 0x84, + 0xee, 0xf3, 0xcd, 0xe8, 0xcd, 0x04, 0x77, 0x9f, 0x89, 0x0c, 0xc9, 0xa1, 0x5d, 0x7d, 0xfc, 0x08, + 0x73, 0xb1, 0xa5, 0xbf, 0xbc, 0x02, 0x19, 0xb1, 0x32, 0xbf, 0xab, 0xc0, 0x84, 0xfc, 0x8c, 0x95, + 0x54, 0x47, 0x7b, 0xa8, 0x1a, 0x4d, 0xeb, 0x72, 0x6d, 0x64, 0x7e, 0xde, 0x3d, 0xf5, 0x85, 0x4f, + 0xff, 0xf1, 0x3f, 0x7f, 0x6b, 0xec, 0x59, 0x52, 0xa9, 0x09, 0xaf, 0x51, 0x93, 0x5f, 0xb9, 0xd6, + 0x3e, 0x12, 0x83, 0xf6, 0x31, 0x0b, 0x34, 0xb3, 0x91, 0x37, 0x1b, 0x94, 0xea, 0x4e, 0x3e, 0x8a, + 0x2d, 0xdf, 0x1c, 0x85, 0x55, 0x60, 0x79, 0x09, 0xb1, 0xbc, 0x40, 0xca, 0x31, 0x16, 0x93, 0x73, + 0x74, 0x60, 0x7c, 0x90, 0x27, 0xd9, 0xda, 0x3e, 0x35, 0xec, 0x70, 0x9f, 0xf8, 0x90, 0xc6, 0x47, + 0xa4, 0x64, 0x90, 0x5f, 0x90, 0x9f, 0x9d, 0x96, 0x17, 0x87, 0x33, 0x0a, 0x28, 0xf3, 0x08, 0xa5, + 0x48, 0xa6, 0x62, 0x28, 0x78, 0x25, 0x42, 0x5a, 0x90, 0xc2, 0x7b, 0xae, 0xeb, 0x43, 0x34, 0x45, + 0x2d, 0x8e, 0xf2, 0x90, 0x55, 0xbd, 0x8a, 0x8d, 0x95, 0x49, 0x29, 0xd9, 0x98, 0x64, 0xfc, 0x8f, + 0xf9, 0xa3, 0x55, 0xbc, 0xd2, 0x20, 0x2f, 0x8e, 0x76, 0xf1, 0xc1, 0x01, 0xdc, 0x3a, 0xcb, 0x2d, + 0x89, 0x7a, 0x11, 0x91, 0x4c, 0x93, 0xc9, 0x18, 0x09, 0x3b, 0x5d, 0x91, 0x4f, 0x14, 0xc8, 0xf0, + 0x78, 0x96, 0x0c, 0x7d, 0x5a, 0x14, 0x1b, 0xfb, 0xc6, 0x08, 0x9c, 0xa2, 0xd9, 0x67, 0xb1, 0xd9, + 0x67, 0xc8, 0x65, 0xa9, 0x59, 0xc6, 0x20, 0x59, 0x20, 0x80, 0x0c, 0x7f, 0x3d, 0x32, 0x10, 0x41, + 0xe2, 0x81, 0x49, 0x59, 0xbe, 0xde, 0x15, 0x7f, 0x3d, 0xc2, 0x02, 0x25, 0x61, 0xf5, 0xde, 0x46, + 0xc5, 0x1f, 0x9a, 0x74, 0x1a, 0xfd, 0x8e, 0x02, 0xf9, 0xf8, 0xb6, 0x7e, 0xa0, 0xdd, 0xbb, 0xdf, + 0x28, 0x0c, 0xb4, 0x7b, 0xcf, 0x03, 0x02, 0xf5, 0x06, 0x62, 0xb9, 0x46, 0x9e, 0x8d, 0xb1, 0x18, + 0x11, 0x0f, 0xce, 0x05, 0x09, 0xd3, 0xf7, 0x14, 0x98, 0x4a, 0xbe, 0xe6, 0x20, 0x23, 0xbd, 0x55, + 0x91, 0xcf, 0x17, 0xe5, 0x57, 0xce, 0x20, 0x21, 0x20, 0xbe, 0x88, 0x10, 0x9f, 0x27, 0xd7, 0xfa, + 0x40, 0xc4, 0xd1, 0xaa, 0x7d, 0x14, 0x85, 0x75, 0x1f, 0x93, 0x5f, 0x53, 0x60, 0x42, 0xce, 0xe4, + 0x0e, 0xf4, 0x63, 0x7d, 0x6e, 0x63, 0x06, 0xfa, 0xb1, 0x7e, 0x99, 0x6a, 0xf5, 0x32, 0xc2, 0x9b, + 0x25, 0x33, 0x31, 0xbc, 0x38, 0xfd, 0xfc, 0x3b, 0x22, 0xd3, 0x8e, 0x6f, 0xc8, 0x7e, 0x74, 0x88, + 0x2a, 0x88, 0xe8, 0x32, 0xb9, 0x14, 0x23, 0xc2, 0xb7, 0x70, 0xba, 0x8c, 0xab, 0x20, 0x25, 0x96, + 0xc9, 0xc0, 0x3f, 0x4a, 0xe8, 0xc9, 0x79, 0x97, 0xab, 0xa3, 0xb2, 0x3f, 0xde, 0xd3, 0x23, 0x17, + 0xbf, 0x07, 0x91, 0x66, 0xd8, 0xef, 0x2b, 0x30, 0x99, 0x48, 0x16, 0x93, 0xda, 0xd0, 0xa6, 0x92, + 0x19, 0xed, 0xf2, 0xcb, 0xa3, 0x0b, 0x3c, 0x76, 0x05, 0x08, 0x74, 0xc2, 0x5c, 0x12, 0xbe, 0x4f, + 0x14, 0xc8, 0xc7, 0x29, 0xda, 0x81, 0xab, 0xb2, 0x3b, 0x4d, 0x3d, 0x70, 0x55, 0xf6, 0x64, 0x7d, + 0xd5, 0x12, 0x62, 0x22, 0x6a, 0xc7, 0x1b, 0x06, 0x9e, 0xe1, 0xbc, 0xae, 0xdc, 0x24, 0xdf, 0xc0, + 0x1d, 0xbb, 0x7e, 0x30, 0xd8, 0x1f, 0x26, 0x5e, 0x7c, 0x94, 0x07, 0xed, 0x52, 0xf2, 0xb3, 0x9f, + 0x3e, 0x8e, 0x29, 0x40, 0x45, 0x92, 0x09, 0x7e, 0x41, 0x81, 0xac, 0x78, 0xd3, 0x31, 0x70, 0x33, + 0x4e, 0xbe, 0xfb, 0x18, 0x1d, 0x82, 0x8a, 0x10, 0xae, 0x48, 0x3b, 0xb1, 0xc7, 0x35, 0x75, 0x61, + 0x88, 0xde, 0x50, 0x0f, 0xc2, 0x90, 0x7c, 0x4c, 0x72, 0x1e, 0x0c, 0x4d, 0xae, 0x49, 0xc2, 0xf0, + 0xdb, 0xcc, 0xcf, 0x48, 0x0f, 0x7d, 0x06, 0xaf, 0xea, 0xde, 0x17, 0x44, 0x83, 0x57, 0x75, 0x9f, + 0x17, 0x44, 0xea, 0x35, 0x44, 0xf5, 0x25, 0xf2, 0x8c, 0xb4, 0xaa, 0x1b, 0x78, 0x00, 0xea, 0x8a, + 0x95, 0x84, 0xf4, 0x40, 0xd3, 0x24, 0x5f, 0x14, 0x95, 0x5f, 0x1a, 0xcc, 0xda, 0xf5, 0x9e, 0x4a, + 0xbd, 0x89, 0x50, 0x9e, 0x23, 0xea, 0x00, 0x28, 0xb5, 0x8f, 0x18, 0xe1, 0x63, 0xf2, 0x0d, 0x48, + 0xdd, 0x77, 0x1b, 0xc1, 0xc0, 0xb8, 0x45, 0x7a, 0x56, 0x76, 0x56, 0x28, 0xfd, 0x7c, 0x5d, 0x43, + 0xb6, 0xc8, 0x6f, 0x2a, 0xf8, 0x07, 0x1b, 0x9d, 0xc4, 0xd8, 0x40, 0x9f, 0xd2, 0xef, 0xfe, 0x62, + 0xa0, 0x4f, 0xe9, 0x9b, 0x73, 0x53, 0x17, 0x10, 0x55, 0x89, 0xcc, 0xcb, 0xb3, 0x98, 0xf1, 0x89, + 0x17, 0x1c, 0x1f, 0x43, 0x9a, 0x6f, 0xa0, 0x2f, 0x0c, 0xcf, 0x78, 0x0c, 0x0f, 0x20, 0x93, 0xdb, + 0xe5, 0x63, 0x42, 0x1a, 0x79, 0x93, 0xfc, 0x3d, 0x16, 0xec, 0x4b, 0x79, 0x0a, 0x32, 0xea, 0x43, + 0xe6, 0x91, 0x82, 0xfd, 0x3e, 0x79, 0x94, 0x3e, 0x33, 0xa6, 0x1b, 0x54, 0xad, 0xde, 0x34, 0x0f, + 0x11, 0xcc, 0xef, 0x2a, 0x50, 0x90, 0x92, 0x23, 0x03, 0x77, 0xa7, 0xde, 0x24, 0x4a, 0xbf, 0xd1, + 0x4a, 0xfc, 0xf9, 0xad, 0x24, 0xa3, 0x51, 0xcf, 0xf5, 0x43, 0xf5, 0x3a, 0x82, 0xbb, 0x4a, 0x16, + 0x3a, 0xd1, 0x7f, 0x47, 0x20, 0xe9, 0xfe, 0x33, 0x3c, 0x51, 0x30, 0xc4, 0xf9, 0x4a, 0x99, 0x9a, + 0x81, 0xc1, 0x68, 0x32, 0xeb, 0xd0, 0xd7, 0xfd, 0x32, 0x06, 0x09, 0xc2, 0x2f, 0x2b, 0x00, 0x9d, + 0xa3, 0x22, 0xb9, 0x35, 0xe2, 0x89, 0x72, 0xf8, 0xd2, 0xea, 0x3d, 0x7f, 0xaa, 0xcf, 0x20, 0x9c, + 0x8b, 0x64, 0x56, 0xde, 0x0d, 0x04, 0xd3, 0x8a, 0xfa, 0xd9, 0x7f, 0x2c, 0x5c, 0xf8, 0xec, 0x74, + 0x41, 0xf9, 0xfb, 0xd3, 0x05, 0xe5, 0x5f, 0x4e, 0x17, 0x94, 0x7f, 0x3f, 0x5d, 0x50, 0xbe, 0xfd, + 0x83, 0x85, 0x0b, 0x1f, 0xe4, 0x22, 0x9d, 0xbb, 0x19, 0xcc, 0x0f, 0x7c, 0xf9, 0xff, 0x03, 0x00, + 0x00, 0xff, 0xff, 0x1d, 0xe3, 0x4f, 0x42, 0x93, 0x3d, 0x00, 0x00, } diff --git a/pkg/server/serverpb/status.proto b/pkg/server/serverpb/status.proto index da6f9925063d..6948c0392133 100644 --- a/pkg/server/serverpb/status.proto +++ b/pkg/server/serverpb/status.proto @@ -46,6 +46,8 @@ message CertificateDetails { enum CertificateType { CA = 0; NODE = 1; + CLIENT_CA = 2; + CLIENT = 3; } message Fields { diff --git a/pkg/server/status.go b/pkg/server/status.go index eaa416cd7909..bf0b2f1e4573 100644 --- a/pkg/server/status.go +++ b/pkg/server/status.go @@ -470,11 +470,12 @@ func (s *statusServer) Certificates( switch cert.FileUsage { case security.CAPem: details.Type = serverpb.CertificateDetails_CA + case security.ClientCAPem: + details.Type = serverpb.CertificateDetails_CLIENT_CA case security.NodePem: details.Type = serverpb.CertificateDetails_NODE case security.ClientPem: - // Ignore client certificates for now. - continue + details.Type = serverpb.CertificateDetails_CLIENT default: return nil, errors.Errorf("unknown certificate type %v for file %s", cert.FileUsage, cert.Filename) } diff --git a/pkg/server/status_test.go b/pkg/server/status_test.go index a889324f1578..78bca14f3ea7 100644 --- a/pkg/server/status_test.go +++ b/pkg/server/status_test.go @@ -655,8 +655,8 @@ func TestCertificatesResponse(t *testing.T) { t.Fatal(err) } - // We expect two certificates: CA and node. - if a, e := len(response.Certificates), 2; a != e { + // We expect 4 certificates: CA, node, and client certs for root, testuser. + if a, e := len(response.Certificates), 4; a != e { t.Errorf("expected %d certificates, found %d", e, a) } diff --git a/pkg/sql/create_user.go b/pkg/sql/create_user.go index 47810a06c180..7bfd5251a482 100644 --- a/pkg/sql/create_user.go +++ b/pkg/sql/create_user.go @@ -168,10 +168,11 @@ var blacklistedUsernames = map[string]struct{}{ // NormalizeAndValidateUsername case folds the specified username and verifies // it validates according to the usernameRE regular expression. +// It rejects reserved user names. func NormalizeAndValidateUsername(username string) (string, error) { - username = tree.Name(username).Normalize() - if !usernameRE.MatchString(username) { - return "", errors.Errorf("username %q invalid; %s", username, usernameHelp) + username, err := NormalizeAndValidateUsernameNoBlacklist(username) + if err != nil { + return "", err } if _, ok := blacklistedUsernames[username]; ok { return "", errors.Errorf("username %q reserved", username) @@ -179,6 +180,16 @@ func NormalizeAndValidateUsername(username string) (string, error) { return username, nil } +// NormalizeAndValidateUsernameNoBlacklist case folds the specified username and verifies +// it validates according to the usernameRE regular expression. +func NormalizeAndValidateUsernameNoBlacklist(username string) (string, error) { + username = tree.Name(username).Normalize() + if !usernameRE.MatchString(username) { + return "", errors.Errorf("username %q invalid; %s", username, usernameHelp) + } + return username, nil +} + var errNoUserNameSpecified = errors.New("no username specified") type userAuthInfo struct { diff --git a/pkg/ui/src/views/reports/containers/certificates/index.tsx b/pkg/ui/src/views/reports/containers/certificates/index.tsx index 6d244d79f815..72ea3a39b4be 100644 --- a/pkg/ui/src/views/reports/containers/certificates/index.tsx +++ b/pkg/ui/src/views/reports/containers/certificates/index.tsx @@ -115,7 +115,13 @@ class Certificates extends React.Component { certType = "Certificate Authority"; break; case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.NODE: - certType = "Node"; + certType = "Node Certificate"; + break; + case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.CLIENT_CA: + certType = "Client Certificate Authority"; + break; + case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.CLIENT: + certType = "Client Certificate"; break; default: certType = "Unknown";