From 6306b4c9e261e93bee8646ad83ea9e980309f540 Mon Sep 17 00:00:00 2001 From: cyli Date: Fri, 9 Feb 2018 10:56:39 -0800 Subject: [PATCH 01/15] Vendor in fernet-go to provide AES-128-CBC Signed-off-by: cyli (cherry picked from commit 0f2184530b4dae01d05c04aac7be162792789b2e) --- vendor.conf | 5 +- vendor/github.com/fernet/fernet-go/License | 20 +++ vendor/github.com/fernet/fernet-go/Readme | 22 +++ vendor/github.com/fernet/fernet-go/fernet.go | 168 +++++++++++++++++++ vendor/github.com/fernet/fernet-go/key.go | 91 ++++++++++ 5 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/fernet/fernet-go/License create mode 100644 vendor/github.com/fernet/fernet-go/Readme create mode 100644 vendor/github.com/fernet/fernet-go/fernet.go create mode 100644 vendor/github.com/fernet/fernet-go/key.go diff --git a/vendor.conf b/vendor.conf index 8949ea01fc..da010d405a 100644 --- a/vendor.conf +++ b/vendor.conf @@ -24,10 +24,10 @@ github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/docker/go-units 954fed01cc617c55d838fa2230073f2cb17386c8 github.com/docker/libkv 9fd56606e928ff1f309808f5d5a0b7a2ef73f9a8 -github.com/docker/libnetwork 21544598c53fa36a3c771a8725c643dd2340f845 +github.com/docker/libnetwork 21544598c53fa36a3c771a8725c643dd2340f845 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/opencontainers/runc d40db12e72a40109dfcf28539f5ee0930d2f0277 -github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 +github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 github.com/opencontainers/image-spec v1.0.0 # containerd executor @@ -44,6 +44,7 @@ github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 github.com/boltdb/bolt e72f08ddb5a52992c0a44c7dda9316c7333938b2 github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a github.com/dustin/go-humanize 8929fe90cee4b2cb9deb468b51fb34eba64d1bf0 +github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2 github.com/google/certificate-transparency 0f6e3d1d1ba4d03fdaab7cd716f36255c2e48341 github.com/hashicorp/go-immutable-radix 8e8ed81f8f0bf1bdd829593fdd5c29922c1ea990 github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad diff --git a/vendor/github.com/fernet/fernet-go/License b/vendor/github.com/fernet/fernet-go/License new file mode 100644 index 0000000000..14214fbe0f --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/License @@ -0,0 +1,20 @@ +Copyright © 2013 Keith Rarick + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fernet/fernet-go/Readme b/vendor/github.com/fernet/fernet-go/Readme new file mode 100644 index 0000000000..50cc26cfd4 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/Readme @@ -0,0 +1,22 @@ +Fernet takes a user-provided *message* (an arbitrary sequence of +bytes), a *key* (256 bits), and the current time, and produces a +*token*, which contains the message in a form that can't be read +or altered without the key. + +This package is compatible with the other implementations at +https://github.com/fernet. They can exchange tokens freely among +each other. + +Documentation: http://godoc.org/github.com/fernet/fernet-go + + +INSTALL + + $ go get github.com/fernet/fernet-go + + +For more information and background, see the Fernet spec at +https://github.com/fernet/spec. + +Fernet is distributed under the terms of the MIT license. +See the License file for details. diff --git a/vendor/github.com/fernet/fernet-go/fernet.go b/vendor/github.com/fernet/fernet-go/fernet.go new file mode 100644 index 0000000000..8549e69c45 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/fernet.go @@ -0,0 +1,168 @@ +// Package fernet takes a user-provided message (an arbitrary +// sequence of bytes), a key (256 bits), and the current time, +// and produces a token, which contains the message in a form +// that can't be read or altered without the key. +// +// For more information and background, see the Fernet spec +// at https://github.com/fernet/spec. +// +// Subdirectories in this package provide command-line tools +// for working with Fernet keys and tokens. +package fernet + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/binary" + "io" + "time" +) + +const ( + version byte = 0x80 + tsOffset = 1 + ivOffset = tsOffset + 8 + payOffset = ivOffset + aes.BlockSize + overhead = 1 + 8 + aes.BlockSize + sha256.Size // ver + ts + iv + hmac + maxClockSkew = 60 * time.Second +) + +var encoding = base64.URLEncoding + +// generates a token from msg, writes it into tok, and returns the +// number of bytes generated, which is encodedLen(msg). +// len(tok) must be >= encodedLen(len(msg)) +func gen(tok, msg, iv []byte, ts time.Time, k *Key) int { + tok[0] = version + binary.BigEndian.PutUint64(tok[tsOffset:], uint64(ts.Unix())) + copy(tok[ivOffset:], iv) + p := tok[payOffset:] + n := pad(p, msg, aes.BlockSize) + bc, _ := aes.NewCipher(k.cryptBytes()) + cipher.NewCBCEncrypter(bc, iv).CryptBlocks(p[:n], p[:n]) + genhmac(p[n:n], tok[:payOffset+n], k.signBytes()) + return payOffset + n + sha256.Size +} + +// token length for input msg of length n, not including base64 +func encodedLen(n int) int { + const k = aes.BlockSize + return n/k*k + k + overhead +} + +// max msg length for tok of length n, for binary token (no base64) +// upper bound; not exact +func decodedLen(n int) int { + return n - overhead +} + +// if msg is nil, decrypts in place and returns a slice of tok. +func verify(msg, tok []byte, ttl time.Duration, now time.Time, k *Key) []byte { + if len(tok) < 1 || tok[0] != version { + return nil + } + ts := time.Unix(int64(binary.BigEndian.Uint64(tok[1:])), 0) + if ttl >= 0 && (now.After(ts.Add(ttl)) || ts.After(now.Add(maxClockSkew))) { + return nil + } + n := len(tok) - sha256.Size + var hmac [sha256.Size]byte + genhmac(hmac[:0], tok[:n], k.signBytes()) + if subtle.ConstantTimeCompare(tok[n:], hmac[:]) != 1 { + return nil + } + pay := tok[payOffset : len(tok)-sha256.Size] + if len(pay)%aes.BlockSize != 0 { + return nil + } + if msg != nil { + copy(msg, pay) + pay = msg + } + bc, _ := aes.NewCipher(k.cryptBytes()) + iv := tok[9:][:aes.BlockSize] + cipher.NewCBCDecrypter(bc, iv).CryptBlocks(pay, pay) + return unpad(pay) +} + +// Pads p to a multiple of k using PKCS #7 standard block padding. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +func pad(q, p []byte, k int) int { + n := len(p)/k*k + k + copy(q, p) + c := byte(n - len(p)) + for i := len(p); i < n; i++ { + q[i] = c + } + return n +} + +// Removes PKCS #7 standard block padding from p. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +// This function is the inverse of pad. +// If the padding is not well-formed, unpad returns nil. +func unpad(p []byte) []byte { + c := p[len(p)-1] + for i := len(p) - int(c); i < len(p); i++ { + if i < 0 || p[i] != c { + return nil + } + } + return p[:len(p)-int(c)] +} + +func b64enc(src []byte) []byte { + dst := make([]byte, encoding.EncodedLen(len(src))) + encoding.Encode(dst, src) + return dst +} + +func b64dec(src []byte) []byte { + dst := make([]byte, encoding.DecodedLen(len(src))) + n, err := encoding.Decode(dst, src) + if err != nil { + return nil + } + return dst[:n] +} + +func genhmac(q, p, k []byte) { + h := hmac.New(sha256.New, k) + h.Write(p) + h.Sum(q) +} + +// EncryptAndSign encrypts and signs msg with key k and returns the resulting +// fernet token. If msg contains text, the text should be encoded +// with UTF-8 to follow fernet convention. +func EncryptAndSign(msg []byte, k *Key) (tok []byte, err error) { + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + b := make([]byte, encodedLen(len(msg))) + n := gen(b, msg, iv, time.Now(), k) + tok = make([]byte, encoding.EncodedLen(n)) + encoding.Encode(tok, b[:n]) + return tok, nil +} + +// VerifyAndDecrypt verifies that tok is a valid fernet token that was signed +// with a key in k at most ttl time ago only if ttl is greater than zero. +// Returns the message contained in tok if tok is valid, otherwise nil. +func VerifyAndDecrypt(tok []byte, ttl time.Duration, k []*Key) (msg []byte) { + b := make([]byte, encoding.DecodedLen(len(tok))) + n, _ := encoding.Decode(b, tok) + for _, k1 := range k { + msg = verify(nil, b[:n], ttl, time.Now(), k1) + if msg != nil { + return msg + } + } + return nil +} diff --git a/vendor/github.com/fernet/fernet-go/key.go b/vendor/github.com/fernet/fernet-go/key.go new file mode 100644 index 0000000000..595217ed60 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/key.go @@ -0,0 +1,91 @@ +package fernet + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + "io" +) + +var ( + errKeyLen = errors.New("fernet: key decodes to wrong size") + errNoKeys = errors.New("fernet: no keys provided") +) + +// Key represents a key. +type Key [32]byte + +func (k *Key) cryptBytes() []byte { + return k[len(k)/2:] +} + +func (k *Key) signBytes() []byte { + return k[:len(k)/2] +} + +// Generate initializes k with pseudorandom data from package crypto/rand. +func (k *Key) Generate() error { + _, err := io.ReadFull(rand.Reader, k[:]) + return err +} + +// Encode returns the URL-safe base64 encoding of k. +func (k *Key) Encode() string { + return encoding.EncodeToString(k[:]) +} + +// DecodeKey decodes a key from s and returns it. The key can be in +// hexadecimal, standard base64, or URL-safe base64. +func DecodeKey(s string) (*Key, error) { + var b []byte + var err error + if s == "" { + return nil, errors.New("empty key") + } + if len(s) == hex.EncodedLen(len(Key{})) { + b, err = hex.DecodeString(s) + } else { + b, err = base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.URLEncoding.DecodeString(s) + } + } + if err != nil { + return nil, err + } + if len(b) != len(Key{}) { + return nil, errKeyLen + } + k := new(Key) + copy(k[:], b) + return k, nil +} + +// DecodeKeys decodes each element of a using DecodeKey and returns the +// resulting keys. Requires at least one key. +func DecodeKeys(a ...string) ([]*Key, error) { + if len(a) == 0 { + return nil, errNoKeys + } + var err error + ks := make([]*Key, len(a)) + for i, s := range a { + ks[i], err = DecodeKey(s) + if err != nil { + return nil, err + } + } + return ks, nil +} + +// MustDecodeKeys is like DecodeKeys, but panics if an error occurs. +// It simplifies safe initialization of global variables holding +// keys. +func MustDecodeKeys(a ...string) []*Key { + k, err := DecodeKeys(a...) + if err != nil { + panic(err) + } + return k +} From 80c7df1493bcaeebb37999bbb770cba15fafb616 Mon Sep 17 00:00:00 2001 From: cyli Date: Fri, 9 Feb 2018 13:55:57 -0800 Subject: [PATCH 02/15] Add fernet encryption mechanism for raft WAL and snapshots. Signed-off-by: cyli (cherry picked from commit 25c657557da4cedd56ded0bee00a89505c17c05e) --- api/api.pb.txt | 7 + api/types.pb.go | 640 +++++++++++++++--------------- api/types.proto | 1 + manager/encryption/fernet.go | 54 +++ manager/encryption/fernet_test.go | 77 ++++ manager/encryption/nacl_test.go | 29 +- 6 files changed, 479 insertions(+), 329 deletions(-) create mode 100644 manager/encryption/fernet.go create mode 100644 manager/encryption/fernet_test.go diff --git a/api/api.pb.txt b/api/api.pb.txt index 6d5dce80a2..251e262720 100755 --- a/api/api.pb.txt +++ b/api/api.pb.txt @@ -3928,6 +3928,13 @@ file { 66001: "NACLSecretboxSalsa20Poly1305" } } + value { + name: "FERNET_AES_128_CBC" + number: 2 + options { + 66001: "FernetAES128CBC" + } + } } } message_type { diff --git a/api/types.pb.go b/api/types.pb.go index f7d0cdd27e..3f583261b6 100644 --- a/api/types.pb.go +++ b/api/types.pb.go @@ -578,15 +578,18 @@ type MaybeEncryptedRecord_Algorithm int32 const ( MaybeEncryptedRecord_NotEncrypted MaybeEncryptedRecord_Algorithm = 0 MaybeEncryptedRecord_NACLSecretboxSalsa20Poly1305 MaybeEncryptedRecord_Algorithm = 1 + MaybeEncryptedRecord_FernetAES128CBC MaybeEncryptedRecord_Algorithm = 2 ) var MaybeEncryptedRecord_Algorithm_name = map[int32]string{ 0: "NONE", 1: "SECRETBOX_SALSA20_POLY1305", + 2: "FERNET_AES_128_CBC", } var MaybeEncryptedRecord_Algorithm_value = map[string]int32{ "NONE": 0, "SECRETBOX_SALSA20_POLY1305": 1, + "FERNET_AES_128_CBC": 2, } func (x MaybeEncryptedRecord_Algorithm) String() string { @@ -17043,321 +17046,324 @@ var ( func init() { proto.RegisterFile("github.com/docker/swarmkit/api/types.proto", fileDescriptorTypes) } var fileDescriptorTypes = []byte{ - // 5054 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x5a, 0x4d, 0x6c, 0x24, 0x49, - 0x56, 0x76, 0xfd, 0xba, 0xea, 0x55, 0xd9, 0x4e, 0x47, 0x7b, 0x7b, 0xdc, 0xb5, 0xdd, 0x76, 0x4d, - 0xce, 0xf4, 0xce, 0x6c, 0x6f, 0x53, 0xfd, 0xb7, 0xbb, 0xea, 0x99, 0x61, 0x77, 0xa6, 0xfe, 0x6c, - 0xd7, 0xb6, 0x5d, 0x55, 0x8a, 0x2a, 0x77, 0xef, 0x22, 0x41, 0x92, 0xce, 0x0c, 0x97, 0x73, 0x9c, - 0x95, 0x51, 0x64, 0x66, 0xd9, 0x5d, 0x2c, 0x88, 0x16, 0x07, 0x40, 0x3e, 0xc1, 0x89, 0x45, 0xc8, - 0x08, 0x09, 0x8e, 0x48, 0x1c, 0x40, 0x42, 0x70, 0x1a, 0x24, 0x84, 0xf6, 0x06, 0x0b, 0x12, 0x5a, - 0x81, 0x64, 0x58, 0x1f, 0xb8, 0xad, 0xe0, 0x82, 0xb8, 0x80, 0x84, 0xe2, 0x27, 0xb3, 0xd2, 0xd5, - 0x69, 0xbb, 0x87, 0xdd, 0x8b, 0x5d, 0xf1, 0xde, 0xf7, 0x5e, 0x44, 0xbc, 0x88, 0x78, 0xf1, 0xde, - 0x8b, 0x84, 0x7b, 0x03, 0xcb, 0x3f, 0x18, 0xef, 0x55, 0x0c, 0x3a, 0x7c, 0x60, 0x52, 0xe3, 0x90, - 0xb8, 0x0f, 0xbc, 0x63, 0xdd, 0x1d, 0x1e, 0x5a, 0xfe, 0x03, 0x7d, 0x64, 0x3d, 0xf0, 0x27, 0x23, - 0xe2, 0x55, 0x46, 0x2e, 0xf5, 0x29, 0x42, 0x02, 0x50, 0x09, 0x00, 0x95, 0xa3, 0x47, 0xa5, 0xf5, - 0x01, 0xa5, 0x03, 0x9b, 0x3c, 0xe0, 0x88, 0xbd, 0xf1, 0xfe, 0x03, 0xdf, 0x1a, 0x12, 0xcf, 0xd7, - 0x87, 0x23, 0x21, 0x54, 0x5a, 0x9b, 0x05, 0x98, 0x63, 0x57, 0xf7, 0x2d, 0xea, 0x48, 0xfe, 0xca, - 0x80, 0x0e, 0x28, 0xff, 0xf9, 0x80, 0xfd, 0x12, 0x54, 0x75, 0x1d, 0xe6, 0x9f, 0x13, 0xd7, 0xb3, - 0xa8, 0x83, 0x56, 0x20, 0x63, 0x39, 0x26, 0x79, 0xb9, 0x9a, 0x28, 0x27, 0xde, 0x4f, 0x63, 0xd1, - 0x50, 0x1f, 0x02, 0xb4, 0xd8, 0x8f, 0xa6, 0xe3, 0xbb, 0x13, 0xa4, 0x40, 0xea, 0x90, 0x4c, 0x38, - 0x22, 0x8f, 0xd9, 0x4f, 0x46, 0x39, 0xd2, 0xed, 0xd5, 0xa4, 0xa0, 0x1c, 0xe9, 0xb6, 0xfa, 0xa3, - 0x04, 0x14, 0xaa, 0x8e, 0x43, 0x7d, 0xde, 0xbb, 0x87, 0x10, 0xa4, 0x1d, 0x7d, 0x48, 0xa4, 0x10, - 0xff, 0x8d, 0xea, 0x90, 0xb5, 0xf5, 0x3d, 0x62, 0x7b, 0xab, 0xc9, 0x72, 0xea, 0xfd, 0xc2, 0xe3, - 0xaf, 0x54, 0x5e, 0x9f, 0x72, 0x25, 0xa2, 0xa4, 0xb2, 0xcd, 0xd1, 0x7c, 0x10, 0x58, 0x8a, 0xa2, - 0x6f, 0xc2, 0xbc, 0xe5, 0x98, 0x96, 0x41, 0xbc, 0xd5, 0x34, 0xd7, 0xb2, 0x16, 0xa7, 0x65, 0x3a, - 0xfa, 0x5a, 0xfa, 0xfb, 0x67, 0xeb, 0x73, 0x38, 0x10, 0x2a, 0x7d, 0x00, 0x85, 0x88, 0xda, 0x98, - 0xb9, 0xad, 0x40, 0xe6, 0x48, 0xb7, 0xc7, 0x44, 0xce, 0x4e, 0x34, 0x3e, 0x4c, 0x3e, 0x4d, 0xa8, - 0x9f, 0xc0, 0x4a, 0x5b, 0x1f, 0x12, 0x73, 0x93, 0x38, 0xc4, 0xb5, 0x0c, 0x4c, 0x3c, 0x3a, 0x76, - 0x0d, 0xc2, 0xe6, 0x7a, 0x68, 0x39, 0x66, 0x30, 0x57, 0xf6, 0x3b, 0x5e, 0x8b, 0x5a, 0x87, 0xb7, - 0x1a, 0x96, 0x67, 0xb8, 0xc4, 0x27, 0x9f, 0x5b, 0x49, 0x2a, 0x50, 0x72, 0x96, 0x80, 0xa5, 0x59, - 0xe9, 0x9f, 0x83, 0x1b, 0xcc, 0xc4, 0xa6, 0xe6, 0x4a, 0x8a, 0xe6, 0x8d, 0x88, 0xc1, 0x95, 0x15, - 0x1e, 0xbf, 0x1f, 0x67, 0xa1, 0xb8, 0x99, 0x6c, 0xcd, 0xe1, 0x65, 0xae, 0x26, 0x20, 0xf4, 0x46, - 0xc4, 0x40, 0x06, 0xdc, 0x34, 0xe5, 0xa0, 0x67, 0xd4, 0x27, 0xb9, 0xfa, 0xd8, 0x65, 0xbc, 0x64, - 0x9a, 0x5b, 0x73, 0x78, 0x25, 0x50, 0x16, 0xed, 0xa4, 0x06, 0x90, 0x0b, 0x74, 0xab, 0xdf, 0x4b, - 0x40, 0x3e, 0x60, 0x7a, 0xe8, 0xcb, 0x90, 0x77, 0x74, 0x87, 0x6a, 0xc6, 0x68, 0xec, 0xf1, 0x09, - 0xa5, 0x6a, 0xc5, 0xf3, 0xb3, 0xf5, 0x5c, 0x5b, 0x77, 0x68, 0xbd, 0xbb, 0xeb, 0xe1, 0x1c, 0x63, - 0xd7, 0x47, 0x63, 0x0f, 0xbd, 0x0d, 0xc5, 0x21, 0x19, 0x52, 0x77, 0xa2, 0xed, 0x4d, 0x7c, 0xe2, - 0x49, 0xb3, 0x15, 0x04, 0xad, 0xc6, 0x48, 0xe8, 0x1b, 0x30, 0x3f, 0x10, 0x43, 0x5a, 0x4d, 0xf1, - 0xed, 0xf3, 0x4e, 0xdc, 0xe8, 0x67, 0x46, 0x8d, 0x03, 0x19, 0xf5, 0xb7, 0x13, 0xb0, 0x12, 0x52, - 0xc9, 0x2f, 0x8d, 0x2d, 0x97, 0x0c, 0x89, 0xe3, 0x7b, 0xe8, 0x6b, 0x90, 0xb5, 0xad, 0xa1, 0xe5, - 0x7b, 0xd2, 0xe6, 0x77, 0xe2, 0xd4, 0x86, 0x93, 0xc2, 0x12, 0x8c, 0xaa, 0x50, 0x74, 0x89, 0x47, - 0xdc, 0x23, 0xb1, 0xe3, 0xa5, 0x45, 0xaf, 0x11, 0xbe, 0x20, 0xa2, 0x6e, 0x40, 0xae, 0x6b, 0xeb, - 0xfe, 0x3e, 0x75, 0x87, 0x48, 0x85, 0xa2, 0xee, 0x1a, 0x07, 0x96, 0x4f, 0x0c, 0x7f, 0xec, 0x06, - 0xa7, 0xef, 0x02, 0x0d, 0xdd, 0x84, 0x24, 0x15, 0x1d, 0xe5, 0x6b, 0xd9, 0xf3, 0xb3, 0xf5, 0x64, - 0xa7, 0x87, 0x93, 0xd4, 0x53, 0x3f, 0x82, 0xe5, 0xae, 0x3d, 0x1e, 0x58, 0x4e, 0x83, 0x78, 0x86, - 0x6b, 0x8d, 0x98, 0x76, 0xb6, 0x2b, 0x99, 0x8f, 0x0a, 0x76, 0x25, 0xfb, 0x1d, 0x1e, 0xed, 0xe4, - 0xf4, 0x68, 0xab, 0xbf, 0x99, 0x84, 0xe5, 0xa6, 0x33, 0xb0, 0x1c, 0x12, 0x95, 0xbe, 0x0b, 0x8b, - 0x84, 0x13, 0xb5, 0x23, 0xe1, 0x6e, 0xa4, 0x9e, 0x05, 0x41, 0x0d, 0x7c, 0x50, 0x6b, 0xc6, 0x2f, - 0x3c, 0x8a, 0x9b, 0xfe, 0x6b, 0xda, 0x63, 0xbd, 0x43, 0x13, 0xe6, 0x47, 0x7c, 0x12, 0x9e, 0x5c, - 0xde, 0xbb, 0x71, 0xba, 0x5e, 0x9b, 0x67, 0xe0, 0x24, 0xa4, 0xec, 0x4f, 0xe2, 0x24, 0xfe, 0x24, - 0x09, 0x4b, 0x6d, 0x6a, 0x5e, 0xb0, 0x43, 0x09, 0x72, 0x07, 0xd4, 0xf3, 0x23, 0x0e, 0x31, 0x6c, - 0xa3, 0xa7, 0x90, 0x1b, 0xc9, 0xe5, 0x93, 0xab, 0x7f, 0x3b, 0x7e, 0xc8, 0x02, 0x83, 0x43, 0x34, - 0xfa, 0x08, 0xf2, 0xc1, 0x91, 0x61, 0xb3, 0x7d, 0x83, 0x8d, 0x33, 0xc5, 0xa3, 0x6f, 0x40, 0x56, - 0x2c, 0xc2, 0x6a, 0x9a, 0x4b, 0xde, 0x7d, 0x23, 0x9b, 0x63, 0x29, 0x84, 0x36, 0x21, 0xe7, 0xdb, - 0x9e, 0x66, 0x39, 0xfb, 0x74, 0x35, 0xc3, 0x15, 0xac, 0xc7, 0x3a, 0x19, 0x6a, 0x92, 0xfe, 0x76, - 0xaf, 0xe5, 0xec, 0xd3, 0x5a, 0xe1, 0xfc, 0x6c, 0x7d, 0x5e, 0x36, 0xf0, 0xbc, 0x6f, 0x7b, 0xec, - 0x87, 0xfa, 0x3b, 0x09, 0x28, 0x44, 0x50, 0xe8, 0x0e, 0x80, 0xef, 0x8e, 0x3d, 0x5f, 0x73, 0x29, - 0xf5, 0xb9, 0xb1, 0x8a, 0x38, 0xcf, 0x29, 0x98, 0x52, 0x1f, 0x55, 0xe0, 0x86, 0x41, 0x5c, 0x5f, - 0xb3, 0x3c, 0x6f, 0x4c, 0x5c, 0xcd, 0x1b, 0xef, 0x7d, 0x4a, 0x0c, 0x9f, 0x1b, 0xae, 0x88, 0x97, - 0x19, 0xab, 0xc5, 0x39, 0x3d, 0xc1, 0x40, 0x4f, 0xe0, 0x66, 0x14, 0x3f, 0x1a, 0xef, 0xd9, 0x96, - 0xa1, 0xb1, 0xc5, 0x4c, 0x71, 0x91, 0x1b, 0x53, 0x91, 0x2e, 0xe7, 0x3d, 0x23, 0x13, 0xf5, 0x87, - 0x09, 0x50, 0xb0, 0xbe, 0xef, 0xef, 0x90, 0xe1, 0x1e, 0x71, 0x7b, 0xbe, 0xee, 0x8f, 0x3d, 0x74, - 0x13, 0xb2, 0x36, 0xd1, 0x4d, 0xe2, 0xf2, 0x41, 0xe5, 0xb0, 0x6c, 0xa1, 0x5d, 0x76, 0x82, 0x75, - 0xe3, 0x40, 0xdf, 0xb3, 0x6c, 0xcb, 0x9f, 0xf0, 0xa1, 0x2c, 0xc6, 0x6f, 0xe1, 0x59, 0x9d, 0x15, - 0x1c, 0x11, 0xc4, 0x17, 0xd4, 0xa0, 0x55, 0x98, 0x1f, 0x12, 0xcf, 0xd3, 0x07, 0x84, 0x8f, 0x34, - 0x8f, 0x83, 0xa6, 0xfa, 0x11, 0x14, 0xa3, 0x72, 0xa8, 0x00, 0xf3, 0xbb, 0xed, 0x67, 0xed, 0xce, - 0x8b, 0xb6, 0x32, 0x87, 0x96, 0xa0, 0xb0, 0xdb, 0xc6, 0xcd, 0x6a, 0x7d, 0xab, 0x5a, 0xdb, 0x6e, - 0x2a, 0x09, 0xb4, 0x00, 0xf9, 0x69, 0x33, 0xa9, 0xfe, 0x59, 0x02, 0x80, 0x99, 0x5b, 0x4e, 0xea, - 0x43, 0xc8, 0x78, 0xbe, 0xee, 0x8b, 0x5d, 0xb9, 0xf8, 0xf8, 0xdd, 0xcb, 0xd6, 0x50, 0x8e, 0x97, - 0xfd, 0x23, 0x58, 0x88, 0x44, 0x47, 0x98, 0xbc, 0x30, 0x42, 0xe6, 0x20, 0x74, 0xd3, 0x74, 0xe5, - 0xc0, 0xf9, 0x6f, 0xf5, 0x23, 0xc8, 0x70, 0xe9, 0x8b, 0xc3, 0xcd, 0x41, 0xba, 0xc1, 0x7e, 0x25, - 0x50, 0x1e, 0x32, 0xb8, 0x59, 0x6d, 0x7c, 0x47, 0x49, 0x22, 0x05, 0x8a, 0x8d, 0x56, 0xaf, 0xde, - 0x69, 0xb7, 0x9b, 0xf5, 0x7e, 0xb3, 0xa1, 0xa4, 0xd4, 0xbb, 0x90, 0x69, 0x0d, 0x99, 0xe6, 0xdb, - 0x6c, 0xcb, 0xef, 0x13, 0x97, 0x38, 0x46, 0x70, 0x92, 0xa6, 0x04, 0xf5, 0xc7, 0x05, 0xc8, 0xec, - 0xd0, 0xb1, 0xe3, 0xa3, 0xc7, 0x11, 0xb7, 0xb5, 0x18, 0x1f, 0x21, 0x70, 0x60, 0xa5, 0x3f, 0x19, - 0x11, 0xe9, 0xd6, 0x6e, 0x42, 0x56, 0x1c, 0x0e, 0x39, 0x1d, 0xd9, 0x62, 0x74, 0x5f, 0x77, 0x07, - 0xc4, 0x97, 0xf3, 0x91, 0x2d, 0xf4, 0x3e, 0xbb, 0xb1, 0x74, 0x93, 0x3a, 0xf6, 0x84, 0x9f, 0xa1, - 0x9c, 0xb8, 0x96, 0x30, 0xd1, 0xcd, 0x8e, 0x63, 0x4f, 0x70, 0xc8, 0x45, 0x5b, 0x50, 0xdc, 0xb3, - 0x1c, 0x53, 0xa3, 0x23, 0xe1, 0xe4, 0x33, 0x97, 0x9f, 0x38, 0x31, 0xaa, 0x9a, 0xe5, 0x98, 0x1d, - 0x01, 0xc6, 0x85, 0xbd, 0x69, 0x03, 0xb5, 0x61, 0xf1, 0x88, 0xda, 0xe3, 0x21, 0x09, 0x75, 0x65, - 0xb9, 0xae, 0xf7, 0x2e, 0xd7, 0xf5, 0x9c, 0xe3, 0x03, 0x6d, 0x0b, 0x47, 0xd1, 0x26, 0x7a, 0x06, - 0x0b, 0xfe, 0x70, 0xb4, 0xef, 0x85, 0xea, 0xe6, 0xb9, 0xba, 0x2f, 0x5d, 0x61, 0x30, 0x06, 0x0f, - 0xb4, 0x15, 0xfd, 0x48, 0x0b, 0x6d, 0x42, 0xc1, 0xa0, 0x8e, 0x67, 0x79, 0x3e, 0x71, 0x8c, 0xc9, - 0x6a, 0x8e, 0xdb, 0xfe, 0x8a, 0x59, 0xd6, 0xa7, 0x60, 0x1c, 0x95, 0x2c, 0xfd, 0x7a, 0x0a, 0x0a, - 0x11, 0x13, 0xa0, 0x1e, 0x14, 0x46, 0x2e, 0x1d, 0xe9, 0x03, 0x7e, 0xe3, 0xc9, 0x45, 0x7d, 0xf4, - 0x46, 0xe6, 0xab, 0x74, 0xa7, 0x82, 0x38, 0xaa, 0x45, 0x3d, 0x4d, 0x42, 0x21, 0xc2, 0x44, 0xf7, - 0x20, 0x87, 0xbb, 0xb8, 0xf5, 0xbc, 0xda, 0x6f, 0x2a, 0x73, 0xa5, 0xdb, 0x27, 0xa7, 0xe5, 0x55, - 0xae, 0x2d, 0xaa, 0xa0, 0xeb, 0x5a, 0x47, 0x6c, 0x0f, 0xbf, 0x0f, 0xf3, 0x01, 0x34, 0x51, 0xfa, - 0xe2, 0xc9, 0x69, 0xf9, 0xad, 0x59, 0x68, 0x04, 0x89, 0x7b, 0x5b, 0x55, 0xdc, 0x6c, 0x28, 0xc9, - 0x78, 0x24, 0xee, 0x1d, 0xe8, 0x2e, 0x31, 0xd1, 0x97, 0x20, 0x2b, 0x81, 0xa9, 0x52, 0xe9, 0xe4, - 0xb4, 0x7c, 0x73, 0x16, 0x38, 0xc5, 0xe1, 0xde, 0x76, 0xf5, 0x79, 0x53, 0x49, 0xc7, 0xe3, 0x70, - 0xcf, 0xd6, 0x8f, 0x08, 0x7a, 0x17, 0x32, 0x02, 0x96, 0x29, 0xdd, 0x3a, 0x39, 0x2d, 0x7f, 0xe1, - 0x35, 0x75, 0x0c, 0x55, 0x5a, 0xfd, 0xad, 0x3f, 0x5a, 0x9b, 0xfb, 0xab, 0x3f, 0x5e, 0x53, 0x66, - 0xd9, 0xa5, 0xff, 0x49, 0xc0, 0xc2, 0x85, 0xbd, 0x83, 0x54, 0xc8, 0x3a, 0xd4, 0xa0, 0x23, 0x71, - 0x11, 0xe6, 0x6a, 0x70, 0x7e, 0xb6, 0x9e, 0x6d, 0xd3, 0x3a, 0x1d, 0x4d, 0xb0, 0xe4, 0xa0, 0x67, - 0x33, 0x57, 0xf9, 0x93, 0x37, 0xdc, 0x98, 0xb1, 0x97, 0xf9, 0xc7, 0xb0, 0x60, 0xba, 0xd6, 0x11, - 0x71, 0x35, 0x83, 0x3a, 0xfb, 0xd6, 0x40, 0x5e, 0x72, 0xa5, 0xd8, 0x78, 0x93, 0x03, 0x71, 0x51, - 0x08, 0xd4, 0x39, 0xfe, 0x27, 0xb8, 0xc6, 0x4b, 0xcf, 0xa1, 0x18, 0xdd, 0xea, 0xec, 0x5e, 0xf2, - 0xac, 0x5f, 0x26, 0x32, 0xb0, 0xe4, 0x61, 0x28, 0xce, 0x33, 0x8a, 0x08, 0x2b, 0xdf, 0x83, 0xf4, - 0x90, 0x9a, 0x42, 0xcf, 0x42, 0xed, 0x06, 0x8b, 0x26, 0xfe, 0xf9, 0x6c, 0xbd, 0x40, 0xbd, 0xca, - 0x86, 0x65, 0x93, 0x1d, 0x6a, 0x12, 0xcc, 0x01, 0xea, 0x11, 0xa4, 0x99, 0xcf, 0x41, 0x5f, 0x84, - 0x74, 0xad, 0xd5, 0x6e, 0x28, 0x73, 0xa5, 0xe5, 0x93, 0xd3, 0xf2, 0x02, 0x37, 0x09, 0x63, 0xb0, - 0xbd, 0x8b, 0xd6, 0x21, 0xfb, 0xbc, 0xb3, 0xbd, 0xbb, 0xc3, 0xb6, 0xd7, 0x8d, 0x93, 0xd3, 0xf2, - 0x52, 0xc8, 0x16, 0x46, 0x43, 0x77, 0x20, 0xd3, 0xdf, 0xe9, 0x6e, 0xf4, 0x94, 0x64, 0x09, 0x9d, - 0x9c, 0x96, 0x17, 0x43, 0x3e, 0x1f, 0x73, 0x69, 0x59, 0xae, 0x6a, 0x3e, 0xa4, 0xab, 0x3f, 0x48, - 0x40, 0x21, 0x72, 0xe0, 0xd8, 0xc6, 0x6c, 0x34, 0x37, 0xaa, 0xbb, 0xdb, 0x7d, 0x65, 0x2e, 0xb2, - 0x31, 0x23, 0x90, 0x06, 0xd9, 0xd7, 0xc7, 0x36, 0xf3, 0x73, 0x50, 0xef, 0xb4, 0x7b, 0xad, 0x5e, - 0xbf, 0xd9, 0xee, 0x2b, 0x89, 0xd2, 0xea, 0xc9, 0x69, 0x79, 0x65, 0x16, 0xbc, 0x31, 0xb6, 0x6d, - 0xb6, 0x35, 0xeb, 0xd5, 0xfa, 0x16, 0xdf, 0xeb, 0xd3, 0xad, 0x19, 0x41, 0xd5, 0x75, 0xe3, 0x80, - 0x98, 0xe8, 0x3e, 0xe4, 0x1b, 0xcd, 0xed, 0xe6, 0x66, 0x95, 0x7b, 0xf7, 0xd2, 0x9d, 0x93, 0xd3, - 0xf2, 0xad, 0xd7, 0x7b, 0xb7, 0xc9, 0x40, 0xf7, 0x89, 0x39, 0xb3, 0x45, 0x23, 0x10, 0xf5, 0xbf, - 0x92, 0xb0, 0x80, 0x59, 0x3a, 0xec, 0xfa, 0x5d, 0x6a, 0x5b, 0xc6, 0x04, 0x75, 0x21, 0x6f, 0x50, - 0xc7, 0xb4, 0x22, 0x7e, 0xe2, 0xf1, 0x25, 0x21, 0xd1, 0x54, 0x2a, 0x68, 0xd5, 0x03, 0x49, 0x3c, - 0x55, 0x82, 0x1e, 0x40, 0xc6, 0x24, 0xb6, 0x3e, 0x91, 0xb1, 0xd9, 0xad, 0x8a, 0x48, 0xb8, 0x2b, - 0x41, 0xc2, 0x5d, 0x69, 0xc8, 0x84, 0x1b, 0x0b, 0x1c, 0xcf, 0x41, 0xf4, 0x97, 0x9a, 0xee, 0xfb, - 0x64, 0x38, 0xf2, 0x45, 0x60, 0x96, 0xc6, 0x85, 0xa1, 0xfe, 0xb2, 0x2a, 0x49, 0xe8, 0x11, 0x64, - 0x8f, 0x2d, 0xc7, 0xa4, 0xc7, 0x32, 0xf6, 0xba, 0x42, 0xa9, 0x04, 0xaa, 0x27, 0x2c, 0x24, 0x99, - 0x19, 0x26, 0xdb, 0x43, 0xed, 0x4e, 0xbb, 0x19, 0xec, 0x21, 0xc9, 0xef, 0x38, 0x6d, 0xea, 0xb0, - 0xf3, 0x0f, 0x9d, 0xb6, 0xb6, 0x51, 0x6d, 0x6d, 0xef, 0x62, 0xb6, 0x8f, 0x56, 0x4e, 0x4e, 0xcb, - 0x4a, 0x08, 0xd9, 0xd0, 0x2d, 0x9b, 0x25, 0x03, 0xb7, 0x20, 0x55, 0x6d, 0x7f, 0x47, 0x49, 0x96, - 0x94, 0x93, 0xd3, 0x72, 0x31, 0x64, 0x57, 0x9d, 0xc9, 0xd4, 0xee, 0xb3, 0xfd, 0xaa, 0x7f, 0x97, - 0x82, 0xe2, 0xee, 0xc8, 0xd4, 0x7d, 0x22, 0xce, 0x19, 0x2a, 0x43, 0x61, 0xa4, 0xbb, 0xba, 0x6d, - 0x13, 0xdb, 0xf2, 0x86, 0xb2, 0x94, 0x10, 0x25, 0xa1, 0x0f, 0xde, 0xd4, 0x8c, 0xb5, 0x1c, 0x3b, - 0x3b, 0xdf, 0xfb, 0xd7, 0xf5, 0x44, 0x60, 0xd0, 0x5d, 0x58, 0xdc, 0x17, 0xa3, 0xd5, 0x74, 0x83, - 0x2f, 0x6c, 0x8a, 0x2f, 0x6c, 0x25, 0x6e, 0x61, 0xa3, 0xc3, 0xaa, 0xc8, 0x49, 0x56, 0xb9, 0x14, - 0x5e, 0xd8, 0x8f, 0x36, 0xd1, 0x13, 0x98, 0x1f, 0x52, 0xc7, 0xf2, 0xa9, 0x7b, 0xfd, 0x2a, 0x04, - 0x48, 0x74, 0x0f, 0x96, 0xd9, 0xe2, 0x06, 0xe3, 0xe1, 0x6c, 0x7e, 0x9d, 0x27, 0xf1, 0xd2, 0x50, - 0x7f, 0x29, 0x3b, 0xc4, 0x8c, 0x8c, 0x6a, 0x90, 0xa1, 0x2e, 0x8b, 0x17, 0xb3, 0x7c, 0xb8, 0xf7, - 0xaf, 0x1d, 0xae, 0x68, 0x74, 0x98, 0x0c, 0x16, 0xa2, 0xea, 0xd7, 0x61, 0xe1, 0xc2, 0x24, 0x58, - 0x98, 0xd4, 0xad, 0xee, 0xf6, 0x9a, 0xca, 0x1c, 0x2a, 0x42, 0xae, 0xde, 0x69, 0xf7, 0x5b, 0xed, - 0x5d, 0x16, 0xe7, 0x15, 0x21, 0x87, 0x3b, 0xdb, 0xdb, 0xb5, 0x6a, 0xfd, 0x99, 0x92, 0x54, 0x2b, - 0x50, 0x88, 0x68, 0x43, 0x8b, 0x00, 0xbd, 0x7e, 0xa7, 0xab, 0x6d, 0xb4, 0x70, 0xaf, 0x2f, 0xa2, - 0xc4, 0x5e, 0xbf, 0x8a, 0xfb, 0x92, 0x90, 0x50, 0xff, 0x23, 0x19, 0xac, 0xa8, 0x0c, 0x0c, 0x6b, - 0x17, 0x03, 0xc3, 0x2b, 0x06, 0x2f, 0x43, 0xc3, 0x69, 0x23, 0x0c, 0x10, 0x3f, 0x00, 0xe0, 0x1b, - 0x87, 0x98, 0x9a, 0xee, 0xcb, 0x85, 0x2f, 0xbd, 0x66, 0xe4, 0x7e, 0x50, 0xd1, 0xc2, 0x79, 0x89, - 0xae, 0xfa, 0xe8, 0x1b, 0x50, 0x34, 0xe8, 0x70, 0x64, 0x13, 0x29, 0x9c, 0xba, 0x56, 0xb8, 0x10, - 0xe2, 0xab, 0x7e, 0x34, 0x34, 0x4d, 0x5f, 0x0c, 0x9e, 0x7f, 0x23, 0x11, 0x58, 0x26, 0x26, 0x1a, - 0x2d, 0x42, 0x6e, 0xb7, 0xdb, 0xa8, 0xf6, 0x5b, 0xed, 0x4d, 0x25, 0x81, 0x00, 0xb2, 0xdc, 0xd4, - 0x0d, 0x25, 0xc9, 0xa2, 0xe8, 0x7a, 0x67, 0xa7, 0xbb, 0xdd, 0xe4, 0x1e, 0x0b, 0xad, 0x80, 0x12, - 0x18, 0x5b, 0xe3, 0x86, 0x6c, 0x36, 0x94, 0x34, 0xba, 0x01, 0x4b, 0x21, 0x55, 0x4a, 0x66, 0xd0, - 0x4d, 0x40, 0x21, 0x71, 0xaa, 0x22, 0xab, 0xfe, 0x2a, 0x2c, 0xd5, 0xa9, 0xe3, 0xeb, 0x96, 0x13, - 0x66, 0x18, 0x8f, 0xd9, 0xa4, 0x25, 0x49, 0xb3, 0x64, 0x25, 0xa8, 0xb6, 0x74, 0x7e, 0xb6, 0x5e, - 0x08, 0xa1, 0xad, 0x06, 0x0f, 0x95, 0x64, 0xc3, 0x64, 0xe7, 0x77, 0x64, 0x99, 0xdc, 0xb8, 0x99, - 0xda, 0xfc, 0xf9, 0xd9, 0x7a, 0xaa, 0xdb, 0x6a, 0x60, 0x46, 0x43, 0x5f, 0x84, 0x3c, 0x79, 0x69, - 0xf9, 0x9a, 0xc1, 0xee, 0x25, 0x66, 0xc0, 0x0c, 0xce, 0x31, 0x42, 0x9d, 0x5d, 0x43, 0x35, 0x80, - 0x2e, 0x75, 0x7d, 0xd9, 0xf3, 0x57, 0x21, 0x33, 0xa2, 0x2e, 0xaf, 0x5d, 0x5c, 0x5a, 0x51, 0x63, - 0x70, 0xb1, 0x51, 0xb1, 0x00, 0xab, 0xbf, 0x97, 0x02, 0xe8, 0xeb, 0xde, 0xa1, 0x54, 0xf2, 0x14, - 0xf2, 0x61, 0x75, 0x52, 0x16, 0x41, 0xae, 0x5c, 0xed, 0x10, 0x8c, 0x9e, 0x04, 0x9b, 0x4d, 0xe4, - 0x4e, 0xb1, 0x49, 0x6c, 0xd0, 0x51, 0x5c, 0xfa, 0x71, 0x31, 0x41, 0x62, 0xd7, 0x3c, 0x71, 0x5d, - 0xb9, 0xf2, 0xec, 0x27, 0xaa, 0xf3, 0x6b, 0x41, 0x18, 0x4d, 0x46, 0xdf, 0xb1, 0x65, 0x9f, 0x99, - 0x15, 0xd9, 0x9a, 0xc3, 0x53, 0x39, 0xf4, 0x31, 0x14, 0xd8, 0xbc, 0x35, 0x8f, 0xf3, 0x64, 0xe0, - 0x7d, 0xa9, 0xa9, 0x84, 0x06, 0x0c, 0xa3, 0xa9, 0x95, 0xef, 0x00, 0xe8, 0xa3, 0x91, 0x6d, 0x11, - 0x53, 0xdb, 0x9b, 0xf0, 0x48, 0x3b, 0x8f, 0xf3, 0x92, 0x52, 0x9b, 0xb0, 0xe3, 0x12, 0xb0, 0x75, - 0x9f, 0x47, 0xcf, 0xd7, 0x18, 0x50, 0xa2, 0xab, 0x7e, 0x4d, 0x81, 0x45, 0x77, 0xec, 0x30, 0x83, - 0xca, 0xd1, 0xa9, 0x7f, 0x9a, 0x84, 0xb7, 0xda, 0xc4, 0x3f, 0xa6, 0xee, 0x61, 0xd5, 0xf7, 0x75, - 0xe3, 0x60, 0x48, 0x1c, 0xb9, 0x7c, 0x91, 0x84, 0x26, 0x71, 0x21, 0xa1, 0x59, 0x85, 0x79, 0xdd, - 0xb6, 0x74, 0x8f, 0x88, 0xe0, 0x2d, 0x8f, 0x83, 0x26, 0x4b, 0xbb, 0x58, 0x12, 0x47, 0x3c, 0x8f, - 0x88, 0xba, 0x0a, 0x1b, 0x78, 0x40, 0x40, 0xdf, 0x85, 0x9b, 0x32, 0x4c, 0xd3, 0xc3, 0xae, 0x58, - 0x42, 0x11, 0x14, 0x68, 0x9b, 0xb1, 0x59, 0x65, 0xfc, 0xe0, 0x64, 0x1c, 0x37, 0x25, 0x77, 0x46, - 0xbe, 0x8c, 0x0a, 0x57, 0xcc, 0x18, 0x56, 0x69, 0x13, 0x6e, 0x5d, 0x2a, 0xf2, 0xb9, 0xea, 0x36, - 0xff, 0x98, 0x04, 0x68, 0x75, 0xab, 0x3b, 0xd2, 0x48, 0x0d, 0xc8, 0xee, 0xeb, 0x43, 0xcb, 0x9e, - 0x5c, 0xe5, 0x01, 0xa7, 0xf8, 0x4a, 0x55, 0x98, 0x63, 0x83, 0xcb, 0x60, 0x29, 0xcb, 0x73, 0xca, - 0xf1, 0x9e, 0x43, 0xfc, 0x30, 0xa7, 0xe4, 0x2d, 0x36, 0x0c, 0x57, 0x77, 0xc2, 0xad, 0x2b, 0x1a, - 0x6c, 0x01, 0x58, 0xc8, 0x73, 0xac, 0x4f, 0x02, 0xb7, 0x25, 0x9b, 0x68, 0x8b, 0x57, 0x47, 0x89, - 0x7b, 0x44, 0xcc, 0xd5, 0x0c, 0x37, 0xea, 0x75, 0xe3, 0xc1, 0x12, 0x2e, 0x6c, 0x17, 0x4a, 0x97, - 0x3e, 0xe2, 0x21, 0xd3, 0x94, 0xf5, 0xb9, 0x6c, 0xf4, 0x10, 0x16, 0x2e, 0xcc, 0xf3, 0xb5, 0x64, - 0xbe, 0xd5, 0x7d, 0xfe, 0x55, 0x25, 0x2d, 0x7f, 0x7d, 0x5d, 0xc9, 0xaa, 0x7f, 0x9b, 0x12, 0x8e, - 0x46, 0x5a, 0x35, 0xfe, 0x55, 0x20, 0xc7, 0x77, 0xb7, 0x41, 0x6d, 0xe9, 0x00, 0xde, 0xbb, 0xda, - 0xff, 0xb0, 0x9c, 0x8e, 0xc3, 0x71, 0x28, 0x88, 0xd6, 0xa1, 0x20, 0x76, 0xb1, 0xc6, 0x0e, 0x1c, - 0x37, 0xeb, 0x02, 0x06, 0x41, 0x62, 0x92, 0xe8, 0x2e, 0x2c, 0xf2, 0xe2, 0x8f, 0x77, 0x40, 0x4c, - 0x81, 0x49, 0x73, 0xcc, 0x42, 0x48, 0xe5, 0xb0, 0x1d, 0x28, 0x4a, 0x82, 0xc6, 0xe3, 0xf9, 0x0c, - 0x1f, 0xd0, 0xbd, 0xeb, 0x06, 0x24, 0x44, 0x78, 0x98, 0x5f, 0x18, 0x4d, 0x1b, 0xea, 0x2f, 0x42, - 0x2e, 0x18, 0x2c, 0x5a, 0x85, 0x54, 0xbf, 0xde, 0x55, 0xe6, 0x4a, 0x4b, 0x27, 0xa7, 0xe5, 0x42, - 0x40, 0xee, 0xd7, 0xbb, 0x8c, 0xb3, 0xdb, 0xe8, 0x2a, 0x89, 0x8b, 0x9c, 0xdd, 0x46, 0x17, 0x95, - 0x20, 0xdd, 0xab, 0xf7, 0xbb, 0x41, 0x7c, 0x16, 0xb0, 0x18, 0xad, 0x94, 0x66, 0xf1, 0x99, 0xba, - 0x0f, 0x85, 0x48, 0xef, 0xe8, 0x1d, 0x98, 0x6f, 0xb5, 0x37, 0x71, 0xb3, 0xd7, 0x53, 0xe6, 0x4a, - 0x37, 0x4f, 0x4e, 0xcb, 0x28, 0xc2, 0x6d, 0x39, 0x03, 0xb6, 0x76, 0xe8, 0x0e, 0xa4, 0xb7, 0x3a, - 0xec, 0xde, 0x17, 0xc9, 0x45, 0x04, 0xb1, 0x45, 0x3d, 0xbf, 0x74, 0x43, 0x06, 0x7e, 0x51, 0xc5, - 0xea, 0xef, 0x27, 0x20, 0x2b, 0x0e, 0x5a, 0xec, 0x22, 0x56, 0x61, 0x3e, 0x28, 0x21, 0x88, 0xc4, - 0xef, 0xbd, 0xcb, 0x93, 0xb4, 0x8a, 0xcc, 0xa9, 0xc4, 0xd6, 0x0c, 0xe4, 0x4a, 0x1f, 0x42, 0x31, - 0xca, 0xf8, 0x5c, 0x1b, 0xf3, 0xbb, 0x50, 0x60, 0x7b, 0x3f, 0x48, 0xd6, 0x1e, 0x43, 0x56, 0x38, - 0x8b, 0xf0, 0x1e, 0xba, 0x3c, 0x63, 0x94, 0x48, 0xf4, 0x14, 0xe6, 0x45, 0x96, 0x19, 0x54, 0x8e, - 0xd7, 0xae, 0x3e, 0x61, 0x38, 0x80, 0xab, 0x1f, 0x43, 0xba, 0x4b, 0x88, 0xcb, 0x6c, 0xef, 0x50, - 0x93, 0x4c, 0xaf, 0x6e, 0x99, 0x20, 0x9b, 0xa4, 0xd5, 0x60, 0x09, 0xb2, 0x49, 0x5a, 0x66, 0x58, - 0x1b, 0x4b, 0x46, 0x6a, 0x63, 0x7d, 0x28, 0xbe, 0x20, 0xd6, 0xe0, 0xc0, 0x27, 0x26, 0x57, 0x74, - 0x1f, 0xd2, 0x23, 0x12, 0x0e, 0x7e, 0x35, 0x76, 0xf3, 0x11, 0xe2, 0x62, 0x8e, 0x62, 0x3e, 0xe6, - 0x98, 0x4b, 0xcb, 0xe7, 0x0e, 0xd9, 0x52, 0xff, 0x21, 0x09, 0x8b, 0x2d, 0xcf, 0x1b, 0xeb, 0x8e, - 0x11, 0x44, 0x75, 0xdf, 0xbc, 0x18, 0xd5, 0xc5, 0xbe, 0x0b, 0x5d, 0x14, 0xb9, 0x58, 0xf2, 0x93, - 0x37, 0x6b, 0x32, 0xbc, 0x59, 0xd5, 0x1f, 0x27, 0x82, 0xba, 0xde, 0xdd, 0x88, 0x2b, 0x10, 0x39, - 0x62, 0x54, 0x13, 0xd9, 0x75, 0x0e, 0x1d, 0x7a, 0xec, 0xa0, 0xb7, 0x21, 0x83, 0x9b, 0xed, 0xe6, - 0x0b, 0x25, 0x21, 0xb6, 0xe7, 0x05, 0x10, 0x26, 0x0e, 0x39, 0x66, 0x9a, 0xba, 0xcd, 0x76, 0x83, - 0x45, 0x61, 0xc9, 0x18, 0x4d, 0x5d, 0xe2, 0x98, 0x96, 0x33, 0x40, 0xef, 0x40, 0xb6, 0xd5, 0xeb, - 0xed, 0xf2, 0x14, 0xf2, 0xad, 0x93, 0xd3, 0xf2, 0x8d, 0x0b, 0x28, 0x5e, 0xd3, 0x35, 0x19, 0x88, - 0xa5, 0x40, 0x2c, 0x3e, 0x8b, 0x01, 0xb1, 0xd8, 0x5a, 0x80, 0x70, 0xa7, 0x5f, 0xed, 0x37, 0x95, - 0x4c, 0x0c, 0x08, 0x53, 0xf6, 0x57, 0x1e, 0xb7, 0x7f, 0x49, 0x82, 0x52, 0x35, 0x0c, 0x32, 0xf2, - 0x19, 0x5f, 0x66, 0x9d, 0x7d, 0xc8, 0x8d, 0xd8, 0x2f, 0x8b, 0x04, 0x11, 0xd4, 0xd3, 0xd8, 0x97, - 0xcd, 0x19, 0xb9, 0x0a, 0xa6, 0x36, 0xa9, 0x9a, 0x43, 0xcb, 0xf3, 0x2c, 0xea, 0x08, 0x1a, 0x0e, - 0x35, 0x95, 0xfe, 0x33, 0x01, 0x37, 0x62, 0x10, 0xe8, 0x21, 0xa4, 0x5d, 0x6a, 0x07, 0x6b, 0x78, - 0xfb, 0xb2, 0x92, 0x2d, 0x13, 0xc5, 0x1c, 0x89, 0xd6, 0x00, 0xf4, 0xb1, 0x4f, 0x75, 0xde, 0x3f, - 0x5f, 0xbd, 0x1c, 0x8e, 0x50, 0xd0, 0x0b, 0xc8, 0x7a, 0xc4, 0x70, 0x49, 0x10, 0x67, 0x7f, 0xfc, - 0xff, 0x1d, 0x7d, 0xa5, 0xc7, 0xd5, 0x60, 0xa9, 0xae, 0x54, 0x81, 0xac, 0xa0, 0xb0, 0x6d, 0x6f, - 0xea, 0xbe, 0x2e, 0x0b, 0xfa, 0xfc, 0x37, 0xdb, 0x4d, 0xba, 0x3d, 0x08, 0x76, 0x93, 0x6e, 0x0f, - 0xd4, 0xbf, 0x49, 0x02, 0x34, 0x5f, 0xfa, 0xc4, 0x75, 0x74, 0xbb, 0x5e, 0x45, 0xcd, 0xc8, 0xcd, - 0x20, 0x66, 0xfb, 0xe5, 0xd8, 0x57, 0x8a, 0x50, 0xa2, 0x52, 0xaf, 0xc6, 0xdc, 0x0d, 0xb7, 0x20, - 0x35, 0x76, 0xe5, 0x63, 0xb5, 0x88, 0x91, 0x77, 0xf1, 0x36, 0x66, 0x34, 0xd4, 0x9c, 0xba, 0xad, - 0xd4, 0xe5, 0x4f, 0xd2, 0x91, 0x0e, 0x62, 0x5d, 0x17, 0x3b, 0xf9, 0x86, 0xae, 0x19, 0x44, 0xde, - 0x2a, 0x45, 0x71, 0xf2, 0xeb, 0xd5, 0x3a, 0x71, 0x7d, 0x9c, 0x35, 0x74, 0xf6, 0xff, 0x27, 0xf2, - 0x6f, 0xf7, 0x01, 0xa6, 0x53, 0x43, 0x6b, 0x90, 0xa9, 0x6f, 0xf4, 0x7a, 0xdb, 0xca, 0x9c, 0x70, - 0xe0, 0x53, 0x16, 0x27, 0xab, 0x7f, 0x99, 0x84, 0x5c, 0xbd, 0x2a, 0xaf, 0xdc, 0x3a, 0x28, 0xdc, - 0x2b, 0xf1, 0x67, 0x10, 0xf2, 0x72, 0x64, 0xb9, 0x13, 0xe9, 0x58, 0xae, 0x48, 0x78, 0x17, 0x99, - 0x08, 0x1b, 0x75, 0x93, 0x0b, 0x20, 0x0c, 0x45, 0x22, 0x8d, 0xa0, 0x19, 0x7a, 0xe0, 0xe3, 0xd7, - 0xae, 0x36, 0x96, 0x48, 0x5d, 0xa6, 0x6d, 0x0f, 0x17, 0x02, 0x25, 0x75, 0xdd, 0x43, 0x1f, 0xc0, - 0x92, 0x67, 0x0d, 0x1c, 0xcb, 0x19, 0x68, 0x81, 0xf1, 0xf8, 0x9b, 0x4c, 0x6d, 0xf9, 0xfc, 0x6c, - 0x7d, 0xa1, 0x27, 0x58, 0xd2, 0x86, 0x0b, 0x12, 0x59, 0xe7, 0xa6, 0x44, 0x5f, 0x87, 0xc5, 0x88, - 0x28, 0xb3, 0xa2, 0x30, 0xbb, 0x72, 0x7e, 0xb6, 0x5e, 0x0c, 0x25, 0x9f, 0x91, 0x09, 0x2e, 0x86, - 0x82, 0xcf, 0x08, 0xaf, 0xcd, 0xec, 0x53, 0xd7, 0x20, 0x9a, 0xcb, 0xcf, 0x34, 0xbf, 0xdd, 0xd3, - 0xb8, 0xc0, 0x69, 0xe2, 0x98, 0xab, 0xcf, 0xe1, 0x46, 0xc7, 0x35, 0x0e, 0x88, 0xe7, 0x0b, 0x53, - 0x48, 0x2b, 0x7e, 0x0c, 0xb7, 0x7d, 0xdd, 0x3b, 0xd4, 0x0e, 0x2c, 0xcf, 0xa7, 0xee, 0x44, 0x73, - 0x89, 0x4f, 0x1c, 0xc6, 0xd7, 0xf8, 0x43, 0xae, 0x2c, 0x08, 0xde, 0x62, 0x98, 0x2d, 0x01, 0xc1, - 0x01, 0x62, 0x9b, 0x01, 0xd4, 0x16, 0x14, 0x59, 0x0a, 0x23, 0x8b, 0x6a, 0x6c, 0xf6, 0x60, 0xd3, - 0x81, 0xf6, 0xc6, 0xd7, 0x54, 0xde, 0xa6, 0x03, 0xf1, 0x53, 0xfd, 0x36, 0x28, 0x0d, 0xcb, 0x1b, - 0xe9, 0xbe, 0x71, 0x10, 0x54, 0x3a, 0x51, 0x03, 0x94, 0x03, 0xa2, 0xbb, 0xfe, 0x1e, 0xd1, 0x7d, - 0x6d, 0x44, 0x5c, 0x8b, 0x9a, 0xd7, 0xaf, 0xf2, 0x52, 0x28, 0xd2, 0xe5, 0x12, 0xea, 0x7f, 0x27, - 0x00, 0xb0, 0xbe, 0x1f, 0x44, 0x6b, 0x5f, 0x81, 0x65, 0xcf, 0xd1, 0x47, 0xde, 0x01, 0xf5, 0x35, - 0xcb, 0xf1, 0x89, 0x7b, 0xa4, 0xdb, 0xb2, 0xb8, 0xa3, 0x04, 0x8c, 0x96, 0xa4, 0xa3, 0xfb, 0x80, - 0x0e, 0x09, 0x19, 0x69, 0xd4, 0x36, 0xb5, 0x80, 0x29, 0x9e, 0x99, 0xd3, 0x58, 0x61, 0x9c, 0x8e, - 0x6d, 0xf6, 0x02, 0x3a, 0xaa, 0xc1, 0x1a, 0x9b, 0x3e, 0x71, 0x7c, 0xd7, 0x22, 0x9e, 0xb6, 0x4f, - 0x5d, 0xcd, 0xb3, 0xe9, 0xb1, 0xb6, 0x4f, 0x6d, 0x9b, 0x1e, 0x13, 0x37, 0xa8, 0x9b, 0x95, 0x6c, - 0x3a, 0x68, 0x0a, 0xd0, 0x06, 0x75, 0x7b, 0x36, 0x3d, 0xde, 0x08, 0x10, 0x2c, 0xa4, 0x9b, 0xce, - 0xd9, 0xb7, 0x8c, 0xc3, 0x20, 0xa4, 0x0b, 0xa9, 0x7d, 0xcb, 0x38, 0x44, 0xef, 0xc0, 0x02, 0xb1, - 0x09, 0x2f, 0x9f, 0x08, 0x54, 0x86, 0xa3, 0x8a, 0x01, 0x91, 0x81, 0xd4, 0x4f, 0x40, 0x69, 0x3a, - 0x86, 0x3b, 0x19, 0x45, 0xd6, 0xfc, 0x3e, 0x20, 0xe6, 0x24, 0x35, 0x9b, 0x1a, 0x87, 0xda, 0x50, - 0x77, 0xf4, 0x01, 0x1b, 0x97, 0x78, 0xfd, 0x53, 0x18, 0x67, 0x9b, 0x1a, 0x87, 0x3b, 0x92, 0xae, - 0x7e, 0x00, 0xd0, 0x1b, 0xb9, 0x44, 0x37, 0x3b, 0x2c, 0x9a, 0x60, 0xa6, 0xe3, 0x2d, 0xcd, 0x94, - 0xaf, 0xa7, 0xd4, 0x95, 0x47, 0x5d, 0x11, 0x8c, 0x46, 0x48, 0x57, 0x7f, 0x1e, 0x6e, 0x74, 0x6d, - 0xdd, 0xe0, 0x5f, 0x12, 0x74, 0xc3, 0xe7, 0x2c, 0xf4, 0x14, 0xb2, 0x02, 0x2a, 0x57, 0x32, 0xf6, - 0xb8, 0x4d, 0xfb, 0xdc, 0x9a, 0xc3, 0x12, 0x5f, 0x2b, 0x02, 0x4c, 0xf5, 0xa8, 0x7f, 0x9e, 0x80, - 0x7c, 0xa8, 0x1f, 0x95, 0xc5, 0x2b, 0x8d, 0xef, 0xea, 0x96, 0x23, 0x33, 0xfe, 0x3c, 0x8e, 0x92, - 0x50, 0x0b, 0x0a, 0xa3, 0x50, 0xfa, 0xca, 0x78, 0x2e, 0x66, 0xd4, 0x38, 0x2a, 0x8b, 0x3e, 0x84, - 0x7c, 0xf0, 0x5c, 0x1d, 0x78, 0xd8, 0xab, 0x5f, 0xb7, 0xa7, 0x70, 0xf5, 0x9b, 0x00, 0xdf, 0xa2, - 0x96, 0xd3, 0xa7, 0x87, 0xc4, 0xe1, 0xcf, 0xaf, 0x2c, 0x5f, 0x24, 0x81, 0x15, 0x65, 0x8b, 0x97, - 0x01, 0xc4, 0x12, 0x84, 0xaf, 0x90, 0xa2, 0xa9, 0xfe, 0x75, 0x12, 0xb2, 0x98, 0x52, 0xbf, 0x5e, - 0x45, 0x65, 0xc8, 0x4a, 0x3f, 0xc1, 0xef, 0x9f, 0x5a, 0xfe, 0xfc, 0x6c, 0x3d, 0x23, 0x1c, 0x44, - 0xc6, 0xe0, 0x9e, 0x21, 0xe2, 0xc1, 0x93, 0x97, 0x79, 0x70, 0xf4, 0x10, 0x8a, 0x12, 0xa4, 0x1d, - 0xe8, 0xde, 0x81, 0x48, 0xde, 0x6a, 0x8b, 0xe7, 0x67, 0xeb, 0x20, 0x90, 0x5b, 0xba, 0x77, 0x80, - 0x41, 0xa0, 0xd9, 0x6f, 0xd4, 0x84, 0xc2, 0xa7, 0xd4, 0x72, 0x34, 0x9f, 0x4f, 0x42, 0x16, 0x1a, - 0x63, 0xd7, 0x71, 0x3a, 0x55, 0xf9, 0x2d, 0x02, 0x7c, 0x3a, 0x9d, 0x7c, 0x13, 0x16, 0x5c, 0x4a, - 0x7d, 0xe1, 0xb6, 0x2c, 0xea, 0xc8, 0x1a, 0x46, 0x39, 0xb6, 0xb4, 0x4d, 0xa9, 0x8f, 0x25, 0x0e, - 0x17, 0xdd, 0x48, 0x0b, 0x3d, 0x84, 0x15, 0x5b, 0xf7, 0x7c, 0x8d, 0xfb, 0x3b, 0x73, 0xaa, 0x2d, - 0xcb, 0x8f, 0x1a, 0x62, 0xbc, 0x0d, 0xce, 0x0a, 0x24, 0xd4, 0x7f, 0x4a, 0x40, 0x81, 0x4d, 0xc6, - 0xda, 0xb7, 0x0c, 0x16, 0xe4, 0x7d, 0xfe, 0xd8, 0xe3, 0x16, 0xa4, 0x0c, 0xcf, 0x95, 0x46, 0xe5, - 0x97, 0x6f, 0xbd, 0x87, 0x31, 0xa3, 0xa1, 0x4f, 0x20, 0x2b, 0x6b, 0x29, 0x22, 0xec, 0x50, 0xaf, - 0x0f, 0x47, 0xa5, 0x6d, 0xa4, 0x1c, 0xdf, 0xcb, 0xd3, 0xd1, 0x89, 0x4b, 0x00, 0x47, 0x49, 0xe8, - 0x26, 0x24, 0x0d, 0x61, 0x2e, 0xf9, 0xb1, 0x4b, 0xbd, 0x8d, 0x93, 0x86, 0xa3, 0xfe, 0x20, 0x01, - 0x0b, 0xd3, 0x03, 0xcf, 0x76, 0xc0, 0x6d, 0xc8, 0x7b, 0xe3, 0x3d, 0x6f, 0xe2, 0xf9, 0x64, 0x18, - 0x3c, 0x2d, 0x87, 0x04, 0xd4, 0x82, 0xbc, 0x6e, 0x0f, 0xa8, 0x6b, 0xf9, 0x07, 0x43, 0x99, 0xa5, - 0xc6, 0x87, 0x0a, 0x51, 0x9d, 0x95, 0x6a, 0x20, 0x82, 0xa7, 0xd2, 0xc1, 0xbd, 0x2f, 0xbe, 0x3f, - 0xe0, 0xf7, 0xfe, 0xdb, 0x50, 0xb4, 0xf5, 0x21, 0x2f, 0x2e, 0xf9, 0xd6, 0x50, 0xcc, 0x23, 0x8d, - 0x0b, 0x92, 0xd6, 0xb7, 0x86, 0x44, 0x55, 0x21, 0x1f, 0x2a, 0x43, 0x4b, 0x50, 0xa8, 0x36, 0x7b, - 0xda, 0xa3, 0xc7, 0x4f, 0xb5, 0xcd, 0xfa, 0x8e, 0x32, 0x27, 0x63, 0xd3, 0xbf, 0x48, 0xc0, 0x82, - 0x74, 0x47, 0x32, 0xde, 0x7f, 0x07, 0xe6, 0x5d, 0x7d, 0xdf, 0x0f, 0x32, 0x92, 0xb4, 0xd8, 0xd5, - 0xcc, 0xc3, 0xb3, 0x8c, 0x84, 0xb1, 0xe2, 0x33, 0x92, 0xc8, 0xc7, 0x0e, 0xa9, 0x2b, 0x3f, 0x76, - 0x48, 0xff, 0x54, 0x3e, 0x76, 0x50, 0x7f, 0x0d, 0x60, 0xc3, 0xb2, 0x49, 0x5f, 0xd4, 0xa1, 0xe2, - 0xf2, 0x4b, 0x16, 0xc3, 0xc9, 0x3a, 0x67, 0x10, 0xc3, 0xb5, 0x1a, 0x98, 0xd1, 0x18, 0x6b, 0x60, - 0x99, 0xf2, 0x30, 0x72, 0xd6, 0x26, 0x63, 0x0d, 0x2c, 0x33, 0x7c, 0x95, 0x4b, 0x5f, 0xf7, 0x2a, - 0x77, 0x9a, 0x80, 0x25, 0x19, 0xbb, 0x86, 0xee, 0xf7, 0xcb, 0x90, 0x17, 0x61, 0xec, 0x34, 0xa1, - 0xe3, 0x0f, 0xfc, 0x02, 0xd7, 0x6a, 0xe0, 0x9c, 0x60, 0xb7, 0x4c, 0xb4, 0x0e, 0x05, 0x09, 0x8d, - 0x7c, 0x18, 0x05, 0x82, 0xd4, 0x66, 0xc3, 0xff, 0x2a, 0xa4, 0xf7, 0x2d, 0x9b, 0xc8, 0x8d, 0x1e, - 0xeb, 0x00, 0xa6, 0x06, 0xd8, 0x9a, 0xc3, 0x1c, 0x5d, 0xcb, 0x05, 0x85, 0x3a, 0x3e, 0x3e, 0x99, - 0x76, 0x46, 0xc7, 0x27, 0x32, 0xd0, 0x99, 0xf1, 0x09, 0x1c, 0x1b, 0x9f, 0x60, 0x8b, 0xf1, 0x49, - 0x68, 0x74, 0x7c, 0x82, 0xf4, 0x53, 0x19, 0xdf, 0x36, 0xdc, 0xac, 0xd9, 0xba, 0x71, 0x68, 0x5b, - 0x9e, 0x4f, 0xcc, 0xa8, 0xc7, 0x78, 0x0c, 0xd9, 0x0b, 0x41, 0xe7, 0x55, 0x15, 0x4d, 0x89, 0x54, - 0xff, 0x3d, 0x01, 0xc5, 0x2d, 0xa2, 0xdb, 0xfe, 0xc1, 0xb4, 0x6c, 0xe4, 0x13, 0xcf, 0x97, 0x97, - 0x15, 0xff, 0x8d, 0xbe, 0x06, 0xb9, 0x30, 0x26, 0xb9, 0xf6, 0x6d, 0x2e, 0x84, 0xa2, 0x27, 0x30, - 0xcf, 0xce, 0x18, 0x1d, 0x07, 0xc9, 0xce, 0x55, 0xcf, 0x3e, 0x12, 0xc9, 0x2e, 0x19, 0x97, 0xf0, - 0x20, 0x84, 0x6f, 0xa5, 0x0c, 0x0e, 0x9a, 0xe8, 0x67, 0xa1, 0xc8, 0x5f, 0x2d, 0x82, 0x98, 0x2b, - 0x73, 0x9d, 0xce, 0x82, 0x78, 0x78, 0x14, 0xf1, 0xd6, 0xff, 0x26, 0x60, 0x65, 0x47, 0x9f, 0xec, - 0x11, 0xe9, 0x36, 0x88, 0x89, 0x89, 0x41, 0x5d, 0x13, 0x75, 0xa3, 0xee, 0xe6, 0x8a, 0x77, 0xcc, - 0x38, 0xe1, 0x78, 0xaf, 0x13, 0x24, 0x60, 0xc9, 0x48, 0x02, 0xb6, 0x02, 0x19, 0x87, 0x3a, 0x06, - 0x91, 0xbe, 0x48, 0x34, 0x54, 0x2b, 0xea, 0x6a, 0x4a, 0xe1, 0x13, 0x23, 0x2f, 0x40, 0xb5, 0xa9, - 0x1f, 0xf6, 0x86, 0x3e, 0x81, 0x52, 0xaf, 0x59, 0xc7, 0xcd, 0x7e, 0xad, 0xf3, 0x6d, 0xad, 0x57, - 0xdd, 0xee, 0x55, 0x1f, 0x3f, 0xd4, 0xba, 0x9d, 0xed, 0xef, 0x3c, 0x7a, 0xf2, 0xf0, 0x6b, 0x4a, - 0xa2, 0x54, 0x3e, 0x39, 0x2d, 0xdf, 0x6e, 0x57, 0xeb, 0xdb, 0xe2, 0xc4, 0xec, 0xd1, 0x97, 0x3d, - 0xdd, 0xf6, 0xf4, 0xc7, 0x0f, 0xbb, 0xd4, 0x9e, 0x30, 0x0c, 0xdb, 0xd6, 0xc5, 0xe8, 0x7d, 0x15, - 0xbd, 0x86, 0x13, 0x97, 0x5e, 0xc3, 0xd3, 0xdb, 0x3c, 0x79, 0xc9, 0x6d, 0xbe, 0x01, 0x2b, 0x86, - 0x4b, 0x3d, 0x4f, 0x63, 0xd1, 0x3f, 0x31, 0x67, 0xf2, 0x8b, 0x2f, 0x9c, 0x9f, 0xad, 0x2f, 0xd7, - 0x19, 0xbf, 0xc7, 0xd9, 0x52, 0xfd, 0xb2, 0x11, 0x21, 0xf1, 0x9e, 0xd4, 0x3f, 0x48, 0xb1, 0x40, - 0xca, 0x3a, 0xb2, 0x6c, 0x32, 0x20, 0x1e, 0x7a, 0x0e, 0x4b, 0x86, 0x4b, 0x4c, 0x16, 0xd6, 0xeb, - 0x76, 0xf4, 0x03, 0xdb, 0x9f, 0x89, 0x8d, 0x69, 0x42, 0xc1, 0x4a, 0x3d, 0x94, 0xea, 0x8d, 0x88, - 0x81, 0x17, 0x8d, 0x0b, 0x6d, 0xf4, 0x29, 0x2c, 0x79, 0xc4, 0xb6, 0x9c, 0xf1, 0x4b, 0xcd, 0xa0, - 0x8e, 0x4f, 0x5e, 0x06, 0xaf, 0x65, 0xd7, 0xe9, 0xed, 0x35, 0xb7, 0x99, 0x54, 0x5d, 0x08, 0xd5, - 0xd0, 0xf9, 0xd9, 0xfa, 0xe2, 0x45, 0x1a, 0x5e, 0x94, 0x9a, 0x65, 0xbb, 0xd4, 0x86, 0xc5, 0x8b, - 0xa3, 0x41, 0x2b, 0xf2, 0xec, 0x73, 0x17, 0x12, 0x9c, 0x6d, 0x74, 0x1b, 0x72, 0x2e, 0x19, 0x58, - 0x9e, 0xef, 0x0a, 0x33, 0x33, 0x4e, 0x48, 0x61, 0x27, 0x5f, 0x7c, 0x1d, 0x55, 0xfa, 0x15, 0x98, - 0xe9, 0x91, 0x1d, 0x16, 0xd3, 0xf2, 0xf4, 0x3d, 0xa9, 0x32, 0x87, 0x83, 0x26, 0xdb, 0x83, 0x63, - 0x2f, 0x0c, 0xd4, 0xf8, 0x6f, 0x46, 0xe3, 0x11, 0x85, 0xfc, 0x56, 0x8c, 0xc7, 0x0c, 0xc1, 0x47, - 0xa7, 0xe9, 0xc8, 0x47, 0xa7, 0x2b, 0x90, 0xb1, 0xc9, 0x11, 0xb1, 0xc5, 0x5d, 0x8e, 0x45, 0xe3, - 0xde, 0x43, 0x28, 0x06, 0x5f, 0x37, 0xf2, 0xaf, 0x2a, 0x72, 0x90, 0xee, 0x57, 0x7b, 0xcf, 0x94, - 0x39, 0x04, 0x90, 0x15, 0x9b, 0x53, 0xbc, 0xe4, 0xd5, 0x3b, 0xed, 0x8d, 0xd6, 0xa6, 0x92, 0xbc, - 0xf7, 0xbb, 0x69, 0xc8, 0x87, 0x6f, 0x49, 0xec, 0xee, 0x68, 0x37, 0x5f, 0x04, 0xbb, 0x3b, 0xa4, - 0xb7, 0xc9, 0x31, 0x7a, 0x7b, 0x5a, 0x85, 0xfa, 0x44, 0x3c, 0x9e, 0x87, 0xec, 0xa0, 0x02, 0xf5, - 0x2e, 0xe4, 0xaa, 0xbd, 0x5e, 0x6b, 0xb3, 0xdd, 0x6c, 0x28, 0x9f, 0x25, 0x4a, 0x5f, 0x38, 0x39, - 0x2d, 0x2f, 0x87, 0xa0, 0xaa, 0x27, 0x36, 0x1f, 0x47, 0xd5, 0xeb, 0xcd, 0x6e, 0xbf, 0xd9, 0x50, - 0x5e, 0x25, 0x67, 0x51, 0xbc, 0xaa, 0xc2, 0x3f, 0xeb, 0xc9, 0x77, 0x71, 0xb3, 0x5b, 0xc5, 0xac, - 0xc3, 0xcf, 0x92, 0xa2, 0x38, 0x36, 0xed, 0xd1, 0x25, 0x23, 0xdd, 0x65, 0x7d, 0xae, 0x05, 0xdf, - 0xc9, 0xbd, 0x4a, 0x89, 0x4f, 0x3f, 0xa6, 0x0f, 0x63, 0x44, 0x37, 0x27, 0xac, 0x37, 0xfe, 0x22, - 0xc9, 0xd5, 0xa4, 0x66, 0x7a, 0xeb, 0x31, 0xdf, 0xc3, 0xb4, 0xa8, 0x30, 0x8f, 0x77, 0xdb, 0x6d, - 0x06, 0x7a, 0x95, 0x9e, 0x99, 0x1d, 0x1e, 0x3b, 0x2c, 0x63, 0x46, 0x77, 0x21, 0x17, 0x3c, 0x58, - 0x2a, 0x9f, 0xa5, 0x67, 0x06, 0x54, 0x0f, 0x5e, 0x5b, 0x79, 0x87, 0x5b, 0xbb, 0x7d, 0xfe, 0x19, - 0xdf, 0xab, 0xcc, 0x6c, 0x87, 0x07, 0x63, 0xdf, 0xa4, 0xc7, 0x0e, 0x3b, 0xb3, 0xb2, 0x0e, 0xf7, - 0x59, 0x46, 0x14, 0x2d, 0x42, 0x8c, 0x2c, 0xc2, 0xbd, 0x0b, 0x39, 0xdc, 0xfc, 0x96, 0xf8, 0xe2, - 0xef, 0x55, 0x76, 0x46, 0x0f, 0x26, 0x9f, 0x12, 0x83, 0xf5, 0x56, 0x86, 0x2c, 0x6e, 0xee, 0x74, - 0x9e, 0x37, 0x95, 0x3f, 0xcc, 0xce, 0xe8, 0xc1, 0x64, 0x48, 0xf9, 0x77, 0x4f, 0xb9, 0x0e, 0xee, - 0x6e, 0x55, 0xf9, 0xa2, 0xcc, 0xea, 0xe9, 0xb8, 0xa3, 0x03, 0xdd, 0x21, 0xe6, 0xf4, 0x0b, 0x99, - 0x90, 0x75, 0xef, 0x17, 0x20, 0x17, 0xc4, 0xae, 0x68, 0x0d, 0xb2, 0x2f, 0x3a, 0xf8, 0x59, 0x13, - 0x2b, 0x73, 0xc2, 0xca, 0x01, 0xe7, 0x85, 0xc8, 0x3a, 0xca, 0x30, 0xbf, 0x53, 0x6d, 0x57, 0x37, - 0x9b, 0x38, 0x28, 0xa2, 0x07, 0x00, 0x19, 0x80, 0x95, 0x14, 0xd9, 0x41, 0xa8, 0xb3, 0xb6, 0xfa, - 0xfd, 0x1f, 0xad, 0xcd, 0xfd, 0xf0, 0x47, 0x6b, 0x73, 0xaf, 0xce, 0xd7, 0x12, 0xdf, 0x3f, 0x5f, - 0x4b, 0xfc, 0xfd, 0xf9, 0x5a, 0xe2, 0xdf, 0xce, 0xd7, 0x12, 0x7b, 0x59, 0x7e, 0x4d, 0x3c, 0xf9, - 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x9b, 0x2e, 0x8d, 0x2e, 0x32, 0x00, 0x00, + // 5091 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x7a, 0x5d, 0x6c, 0x23, 0x59, + 0x56, 0x7f, 0xec, 0xd8, 0x8e, 0x7d, 0xec, 0x24, 0xd5, 0xb7, 0xb3, 0x3d, 0x69, 0x6f, 0x4f, 0x92, + 0xa9, 0x99, 0xde, 0x99, 0xed, 0x9d, 0xbf, 0xfb, 0x6b, 0x77, 0xd5, 0x33, 0xf3, 0xdf, 0x9d, 0xb1, + 0xcb, 0x95, 0x8e, 0xb7, 0x13, 0xdb, 0xba, 0x76, 0xba, 0x77, 0x91, 0xa0, 0xa8, 0x54, 0xdd, 0x38, + 0x35, 0x29, 0xd7, 0x35, 0x55, 0xe5, 0xa4, 0xcd, 0x82, 0x18, 0xf1, 0x00, 0x28, 0x4f, 0xf0, 0x02, + 0x8b, 0x50, 0x10, 0x12, 0xbc, 0x81, 0xc4, 0x03, 0x48, 0x08, 0x9e, 0x06, 0x09, 0xa1, 0x7d, 0x83, + 0x05, 0x09, 0xad, 0x40, 0x0a, 0x6c, 0x1e, 0x78, 0x5b, 0xc1, 0x0b, 0xe2, 0x85, 0x07, 0x74, 0x3f, + 0xaa, 0x5c, 0x71, 0x57, 0x92, 0x19, 0x76, 0x5f, 0x12, 0xdf, 0x73, 0x7e, 0xe7, 0xdc, 0x7b, 0xcf, + 0xbd, 0xf7, 0xdc, 0x73, 0xce, 0x2d, 0xb8, 0x37, 0x70, 0xc2, 0x83, 0xf1, 0x5e, 0xcd, 0xa2, 0xc3, + 0xfb, 0x36, 0xb5, 0x0e, 0x89, 0x7f, 0x3f, 0x38, 0x36, 0xfd, 0xe1, 0xa1, 0x13, 0xde, 0x37, 0x47, + 0xce, 0xfd, 0x70, 0x32, 0x22, 0x41, 0x6d, 0xe4, 0xd3, 0x90, 0x22, 0x24, 0x00, 0xb5, 0x08, 0x50, + 0x3b, 0x7a, 0x58, 0x5d, 0x1f, 0x50, 0x3a, 0x70, 0xc9, 0x7d, 0x8e, 0xd8, 0x1b, 0xef, 0xdf, 0x0f, + 0x9d, 0x21, 0x09, 0x42, 0x73, 0x38, 0x12, 0x42, 0xd5, 0xb5, 0x59, 0x80, 0x3d, 0xf6, 0xcd, 0xd0, + 0xa1, 0x9e, 0xe4, 0xaf, 0x0c, 0xe8, 0x80, 0xf2, 0x9f, 0xf7, 0xd9, 0x2f, 0x41, 0x55, 0xd7, 0x61, + 0xe1, 0x39, 0xf1, 0x03, 0x87, 0x7a, 0x68, 0x05, 0xf2, 0x8e, 0x67, 0x93, 0x97, 0xab, 0x99, 0x8d, + 0xcc, 0x3b, 0x39, 0x2c, 0x1a, 0xea, 0x03, 0x80, 0x16, 0xfb, 0xa1, 0x7b, 0xa1, 0x3f, 0x41, 0x0a, + 0xcc, 0x1f, 0x92, 0x09, 0x47, 0x94, 0x30, 0xfb, 0xc9, 0x28, 0x47, 0xa6, 0xbb, 0x9a, 0x15, 0x94, + 0x23, 0xd3, 0x55, 0x7f, 0x94, 0x81, 0x72, 0xdd, 0xf3, 0x68, 0xc8, 0x7b, 0x0f, 0x10, 0x82, 0x9c, + 0x67, 0x0e, 0x89, 0x14, 0xe2, 0xbf, 0x91, 0x06, 0x05, 0xd7, 0xdc, 0x23, 0x6e, 0xb0, 0x9a, 0xdd, + 0x98, 0x7f, 0xa7, 0xfc, 0xe8, 0x2b, 0xb5, 0x57, 0xa7, 0x5c, 0x4b, 0x28, 0xa9, 0x6d, 0x73, 0x34, + 0x1f, 0x04, 0x96, 0xa2, 0xe8, 0x9b, 0xb0, 0xe0, 0x78, 0xb6, 0x63, 0x91, 0x60, 0x35, 0xc7, 0xb5, + 0xac, 0xa5, 0x69, 0x99, 0x8e, 0xbe, 0x91, 0xfb, 0xfe, 0xd9, 0xfa, 0x1c, 0x8e, 0x84, 0xaa, 0xef, + 0x41, 0x39, 0xa1, 0x36, 0x65, 0x6e, 0x2b, 0x90, 0x3f, 0x32, 0xdd, 0x31, 0x91, 0xb3, 0x13, 0x8d, + 0xf7, 0xb3, 0x4f, 0x32, 0xea, 0x47, 0xb0, 0xd2, 0x36, 0x87, 0xc4, 0x7e, 0x4a, 0x3c, 0xe2, 0x3b, + 0x16, 0x26, 0x01, 0x1d, 0xfb, 0x16, 0x61, 0x73, 0x3d, 0x74, 0x3c, 0x3b, 0x9a, 0x2b, 0xfb, 0x9d, + 0xae, 0x45, 0xd5, 0xe0, 0xb5, 0xa6, 0x13, 0x58, 0x3e, 0x09, 0xc9, 0xe7, 0x56, 0x32, 0x1f, 0x29, + 0x39, 0xcb, 0xc0, 0xf2, 0xac, 0xf4, 0xcf, 0xc0, 0x4d, 0x66, 0x62, 0xdb, 0xf0, 0x25, 0xc5, 0x08, + 0x46, 0xc4, 0xe2, 0xca, 0xca, 0x8f, 0xde, 0x49, 0xb3, 0x50, 0xda, 0x4c, 0xb6, 0xe6, 0xf0, 0x0d, + 0xae, 0x26, 0x22, 0xf4, 0x46, 0xc4, 0x42, 0x16, 0xdc, 0xb2, 0xe5, 0xa0, 0x67, 0xd4, 0x67, 0xb9, + 0xfa, 0xd4, 0x65, 0xbc, 0x64, 0x9a, 0x5b, 0x73, 0x78, 0x25, 0x52, 0x96, 0xec, 0xa4, 0x01, 0x50, + 0x8c, 0x74, 0xab, 0xdf, 0xcb, 0x40, 0x29, 0x62, 0x06, 0xe8, 0xcb, 0x50, 0xf2, 0x4c, 0x8f, 0x1a, + 0xd6, 0x68, 0x1c, 0xf0, 0x09, 0xcd, 0x37, 0x2a, 0xe7, 0x67, 0xeb, 0xc5, 0xb6, 0xe9, 0x51, 0xad, + 0xbb, 0x1b, 0xe0, 0x22, 0x63, 0x6b, 0xa3, 0x71, 0x80, 0xde, 0x80, 0xca, 0x90, 0x0c, 0xa9, 0x3f, + 0x31, 0xf6, 0x26, 0x21, 0x09, 0xa4, 0xd9, 0xca, 0x82, 0xd6, 0x60, 0x24, 0xf4, 0x0d, 0x58, 0x18, + 0x88, 0x21, 0xad, 0xce, 0xf3, 0xed, 0xf3, 0x66, 0xda, 0xe8, 0x67, 0x46, 0x8d, 0x23, 0x19, 0xf5, + 0x37, 0x33, 0xb0, 0x12, 0x53, 0xc9, 0x2f, 0x8c, 0x1d, 0x9f, 0x0c, 0x89, 0x17, 0x06, 0xe8, 0x6b, + 0x50, 0x70, 0x9d, 0xa1, 0x13, 0x06, 0xd2, 0xe6, 0xaf, 0xa7, 0xa9, 0x8d, 0x27, 0x85, 0x25, 0x18, + 0xd5, 0xa1, 0xe2, 0x93, 0x80, 0xf8, 0x47, 0x62, 0xc7, 0x4b, 0x8b, 0x5e, 0x23, 0x7c, 0x41, 0x44, + 0xdd, 0x84, 0x62, 0xd7, 0x35, 0xc3, 0x7d, 0xea, 0x0f, 0x91, 0x0a, 0x15, 0xd3, 0xb7, 0x0e, 0x9c, + 0x90, 0x58, 0xe1, 0xd8, 0x8f, 0x4e, 0xdf, 0x05, 0x1a, 0xba, 0x05, 0x59, 0x2a, 0x3a, 0x2a, 0x35, + 0x0a, 0xe7, 0x67, 0xeb, 0xd9, 0x4e, 0x0f, 0x67, 0x69, 0xa0, 0x7e, 0x00, 0x37, 0xba, 0xee, 0x78, + 0xe0, 0x78, 0x4d, 0x12, 0x58, 0xbe, 0x33, 0x62, 0xda, 0xd9, 0xae, 0x64, 0x3e, 0x2a, 0xda, 0x95, + 0xec, 0x77, 0x7c, 0xb4, 0xb3, 0xd3, 0xa3, 0xad, 0xfe, 0x7a, 0x16, 0x6e, 0xe8, 0xde, 0xc0, 0xf1, + 0x48, 0x52, 0xfa, 0x2e, 0x2c, 0x11, 0x4e, 0x34, 0x8e, 0x84, 0xbb, 0x91, 0x7a, 0x16, 0x05, 0x35, + 0xf2, 0x41, 0xad, 0x19, 0xbf, 0xf0, 0x30, 0x6d, 0xfa, 0xaf, 0x68, 0x4f, 0xf5, 0x0e, 0x3a, 0x2c, + 0x8c, 0xf8, 0x24, 0x02, 0xb9, 0xbc, 0x77, 0xd3, 0x74, 0xbd, 0x32, 0xcf, 0xc8, 0x49, 0x48, 0xd9, + 0x9f, 0xc4, 0x49, 0xfc, 0x49, 0x16, 0x96, 0xdb, 0xd4, 0xbe, 0x60, 0x87, 0x2a, 0x14, 0x0f, 0x68, + 0x10, 0x26, 0x1c, 0x62, 0xdc, 0x46, 0x4f, 0xa0, 0x38, 0x92, 0xcb, 0x27, 0x57, 0xff, 0x4e, 0xfa, + 0x90, 0x05, 0x06, 0xc7, 0x68, 0xf4, 0x01, 0x94, 0xa2, 0x23, 0xc3, 0x66, 0xfb, 0x19, 0x36, 0xce, + 0x14, 0x8f, 0xbe, 0x01, 0x05, 0xb1, 0x08, 0xab, 0x39, 0x2e, 0x79, 0xf7, 0x33, 0xd9, 0x1c, 0x4b, + 0x21, 0xf4, 0x14, 0x8a, 0xa1, 0x1b, 0x18, 0x8e, 0xb7, 0x4f, 0x57, 0xf3, 0x5c, 0xc1, 0x7a, 0xaa, + 0x93, 0xa1, 0x36, 0xe9, 0x6f, 0xf7, 0x5a, 0xde, 0x3e, 0x6d, 0x94, 0xcf, 0xcf, 0xd6, 0x17, 0x64, + 0x03, 0x2f, 0x84, 0x6e, 0xc0, 0x7e, 0xa8, 0xbf, 0x95, 0x81, 0x72, 0x02, 0x85, 0x5e, 0x07, 0x08, + 0xfd, 0x71, 0x10, 0x1a, 0x3e, 0xa5, 0x21, 0x37, 0x56, 0x05, 0x97, 0x38, 0x05, 0x53, 0x1a, 0xa2, + 0x1a, 0xdc, 0xb4, 0x88, 0x1f, 0x1a, 0x4e, 0x10, 0x8c, 0x89, 0x6f, 0x04, 0xe3, 0xbd, 0x8f, 0x89, + 0x15, 0x72, 0xc3, 0x55, 0xf0, 0x0d, 0xc6, 0x6a, 0x71, 0x4e, 0x4f, 0x30, 0xd0, 0x63, 0xb8, 0x95, + 0xc4, 0x8f, 0xc6, 0x7b, 0xae, 0x63, 0x19, 0x6c, 0x31, 0xe7, 0xb9, 0xc8, 0xcd, 0xa9, 0x48, 0x97, + 0xf3, 0x9e, 0x91, 0x89, 0xfa, 0xc3, 0x0c, 0x28, 0xd8, 0xdc, 0x0f, 0x77, 0xc8, 0x70, 0x8f, 0xf8, + 0xbd, 0xd0, 0x0c, 0xc7, 0x01, 0xba, 0x05, 0x05, 0x97, 0x98, 0x36, 0xf1, 0xf9, 0xa0, 0x8a, 0x58, + 0xb6, 0xd0, 0x2e, 0x3b, 0xc1, 0xa6, 0x75, 0x60, 0xee, 0x39, 0xae, 0x13, 0x4e, 0xf8, 0x50, 0x96, + 0xd2, 0xb7, 0xf0, 0xac, 0xce, 0x1a, 0x4e, 0x08, 0xe2, 0x0b, 0x6a, 0xd0, 0x2a, 0x2c, 0x0c, 0x49, + 0x10, 0x98, 0x03, 0xc2, 0x47, 0x5a, 0xc2, 0x51, 0x53, 0xfd, 0x00, 0x2a, 0x49, 0x39, 0x54, 0x86, + 0x85, 0xdd, 0xf6, 0xb3, 0x76, 0xe7, 0x45, 0x5b, 0x99, 0x43, 0xcb, 0x50, 0xde, 0x6d, 0x63, 0xbd, + 0xae, 0x6d, 0xd5, 0x1b, 0xdb, 0xba, 0x92, 0x41, 0x8b, 0x50, 0x9a, 0x36, 0xb3, 0xea, 0x9f, 0x65, + 0x00, 0x98, 0xb9, 0xe5, 0xa4, 0xde, 0x87, 0x7c, 0x10, 0x9a, 0xa1, 0xd8, 0x95, 0x4b, 0x8f, 0xde, + 0xba, 0x6c, 0x0d, 0xe5, 0x78, 0xd9, 0x3f, 0x82, 0x85, 0x48, 0x72, 0x84, 0xd9, 0x0b, 0x23, 0x64, + 0x0e, 0xc2, 0xb4, 0x6d, 0x5f, 0x0e, 0x9c, 0xff, 0x56, 0x3f, 0x80, 0x3c, 0x97, 0xbe, 0x38, 0xdc, + 0x22, 0xe4, 0x9a, 0xec, 0x57, 0x06, 0x95, 0x20, 0x8f, 0xf5, 0x7a, 0xf3, 0x3b, 0x4a, 0x16, 0x29, + 0x50, 0x69, 0xb6, 0x7a, 0x5a, 0xa7, 0xdd, 0xd6, 0xb5, 0xbe, 0xde, 0x54, 0xe6, 0xd5, 0xbb, 0x90, + 0x6f, 0x0d, 0x99, 0xe6, 0x3b, 0x6c, 0xcb, 0xef, 0x13, 0x9f, 0x78, 0x56, 0x74, 0x92, 0xa6, 0x04, + 0xf5, 0xc7, 0x65, 0xc8, 0xef, 0xd0, 0xb1, 0x17, 0xa2, 0x47, 0x09, 0xb7, 0xb5, 0x94, 0x1e, 0x21, + 0x70, 0x60, 0xad, 0x3f, 0x19, 0x11, 0xe9, 0xd6, 0x6e, 0x41, 0x41, 0x1c, 0x0e, 0x39, 0x1d, 0xd9, + 0x62, 0xf4, 0xd0, 0xf4, 0x07, 0x24, 0x94, 0xf3, 0x91, 0x2d, 0xf4, 0x0e, 0xbb, 0xb1, 0x4c, 0x9b, + 0x7a, 0xee, 0x84, 0x9f, 0xa1, 0xa2, 0xb8, 0x96, 0x30, 0x31, 0xed, 0x8e, 0xe7, 0x4e, 0x70, 0xcc, + 0x45, 0x5b, 0x50, 0xd9, 0x73, 0x3c, 0xdb, 0xa0, 0x23, 0xe1, 0xe4, 0xf3, 0x97, 0x9f, 0x38, 0x31, + 0xaa, 0x86, 0xe3, 0xd9, 0x1d, 0x01, 0xc6, 0xe5, 0xbd, 0x69, 0x03, 0xb5, 0x61, 0xe9, 0x88, 0xba, + 0xe3, 0x21, 0x89, 0x75, 0x15, 0xb8, 0xae, 0xb7, 0x2f, 0xd7, 0xf5, 0x9c, 0xe3, 0x23, 0x6d, 0x8b, + 0x47, 0xc9, 0x26, 0x7a, 0x06, 0x8b, 0xe1, 0x70, 0xb4, 0x1f, 0xc4, 0xea, 0x16, 0xb8, 0xba, 0x2f, + 0x5d, 0x61, 0x30, 0x06, 0x8f, 0xb4, 0x55, 0xc2, 0x44, 0x0b, 0x3d, 0x85, 0xb2, 0x45, 0xbd, 0xc0, + 0x09, 0x42, 0xe2, 0x59, 0x93, 0xd5, 0x22, 0xb7, 0xfd, 0x15, 0xb3, 0xd4, 0xa6, 0x60, 0x9c, 0x94, + 0xac, 0xfe, 0xea, 0x3c, 0x94, 0x13, 0x26, 0x40, 0x3d, 0x28, 0x8f, 0x7c, 0x3a, 0x32, 0x07, 0xfc, + 0xc6, 0x93, 0x8b, 0xfa, 0xf0, 0x33, 0x99, 0xaf, 0xd6, 0x9d, 0x0a, 0xe2, 0xa4, 0x16, 0xf5, 0x34, + 0x0b, 0xe5, 0x04, 0x13, 0xdd, 0x83, 0x22, 0xee, 0xe2, 0xd6, 0xf3, 0x7a, 0x5f, 0x57, 0xe6, 0xaa, + 0x77, 0x4e, 0x4e, 0x37, 0x56, 0xb9, 0xb6, 0xa4, 0x82, 0xae, 0xef, 0x1c, 0xb1, 0x3d, 0xfc, 0x0e, + 0x2c, 0x44, 0xd0, 0x4c, 0xf5, 0x8b, 0x27, 0xa7, 0x1b, 0xaf, 0xcd, 0x42, 0x13, 0x48, 0xdc, 0xdb, + 0xaa, 0x63, 0xbd, 0xa9, 0x64, 0xd3, 0x91, 0xb8, 0x77, 0x60, 0xfa, 0xc4, 0x46, 0x5f, 0x82, 0x82, + 0x04, 0xce, 0x57, 0xab, 0x27, 0xa7, 0x1b, 0xb7, 0x66, 0x81, 0x53, 0x1c, 0xee, 0x6d, 0xd7, 0x9f, + 0xeb, 0x4a, 0x2e, 0x1d, 0x87, 0x7b, 0xae, 0x79, 0x44, 0xd0, 0x5b, 0x90, 0x17, 0xb0, 0x7c, 0xf5, + 0xf6, 0xc9, 0xe9, 0xc6, 0x17, 0x5e, 0x51, 0xc7, 0x50, 0xd5, 0xd5, 0xdf, 0xf8, 0xc3, 0xb5, 0xb9, + 0xbf, 0xfa, 0xa3, 0x35, 0x65, 0x96, 0x5d, 0xfd, 0x9f, 0x0c, 0x2c, 0x5e, 0xd8, 0x3b, 0x48, 0x85, + 0x82, 0x47, 0x2d, 0x3a, 0x12, 0x17, 0x61, 0xb1, 0x01, 0xe7, 0x67, 0xeb, 0x85, 0x36, 0xd5, 0xe8, + 0x68, 0x82, 0x25, 0x07, 0x3d, 0x9b, 0xb9, 0xca, 0x1f, 0x7f, 0xc6, 0x8d, 0x99, 0x7a, 0x99, 0x7f, + 0x08, 0x8b, 0xb6, 0xef, 0x1c, 0x11, 0xdf, 0xb0, 0xa8, 0xb7, 0xef, 0x0c, 0xe4, 0x25, 0x57, 0x4d, + 0x8d, 0x37, 0x39, 0x10, 0x57, 0x84, 0x80, 0xc6, 0xf1, 0x3f, 0xc1, 0x35, 0x5e, 0x7d, 0x0e, 0x95, + 0xe4, 0x56, 0x67, 0xf7, 0x52, 0xe0, 0xfc, 0x22, 0x91, 0x81, 0x25, 0x0f, 0x43, 0x71, 0x89, 0x51, + 0x44, 0x58, 0xf9, 0x36, 0xe4, 0x86, 0xd4, 0x16, 0x7a, 0x16, 0x1b, 0x37, 0x59, 0x34, 0xf1, 0xcf, + 0x67, 0xeb, 0x65, 0x1a, 0xd4, 0x36, 0x1d, 0x97, 0xec, 0x50, 0x9b, 0x60, 0x0e, 0x50, 0x8f, 0x20, + 0xc7, 0x7c, 0x0e, 0xfa, 0x22, 0xe4, 0x1a, 0xad, 0x76, 0x53, 0x99, 0xab, 0xde, 0x38, 0x39, 0xdd, + 0x58, 0xe4, 0x26, 0x61, 0x0c, 0xb6, 0x77, 0xd1, 0x3a, 0x14, 0x9e, 0x77, 0xb6, 0x77, 0x77, 0xd8, + 0xf6, 0xba, 0x79, 0x72, 0xba, 0xb1, 0x1c, 0xb3, 0x85, 0xd1, 0xd0, 0xeb, 0x90, 0xef, 0xef, 0x74, + 0x37, 0x7b, 0x4a, 0xb6, 0x8a, 0x4e, 0x4e, 0x37, 0x96, 0x62, 0x3e, 0x1f, 0x73, 0xf5, 0x86, 0x5c, + 0xd5, 0x52, 0x4c, 0x57, 0x7f, 0x90, 0x81, 0x72, 0xe2, 0xc0, 0xb1, 0x8d, 0xd9, 0xd4, 0x37, 0xeb, + 0xbb, 0xdb, 0x7d, 0x65, 0x2e, 0xb1, 0x31, 0x13, 0x90, 0x26, 0xd9, 0x37, 0xc7, 0x2e, 0xf3, 0x73, + 0xa0, 0x75, 0xda, 0xbd, 0x56, 0xaf, 0xaf, 0xb7, 0xfb, 0x4a, 0xa6, 0xba, 0x7a, 0x72, 0xba, 0xb1, + 0x32, 0x0b, 0xde, 0x1c, 0xbb, 0x2e, 0xdb, 0x9a, 0x5a, 0x5d, 0xdb, 0xe2, 0x7b, 0x7d, 0xba, 0x35, + 0x13, 0x28, 0xcd, 0xb4, 0x0e, 0x88, 0x8d, 0xde, 0x85, 0x52, 0x53, 0xdf, 0xd6, 0x9f, 0xd6, 0xb9, + 0x77, 0xaf, 0xbe, 0x7e, 0x72, 0xba, 0x71, 0xfb, 0xd5, 0xde, 0x5d, 0x32, 0x30, 0x43, 0x62, 0xcf, + 0x6c, 0xd1, 0x04, 0x44, 0xfd, 0xaf, 0x2c, 0x2c, 0x62, 0x96, 0x0e, 0xfb, 0x61, 0x97, 0xba, 0x8e, + 0x35, 0x41, 0x5d, 0x28, 0x59, 0xd4, 0xb3, 0x9d, 0x84, 0x9f, 0x78, 0x74, 0x49, 0x48, 0x34, 0x95, + 0x8a, 0x5a, 0x5a, 0x24, 0x89, 0xa7, 0x4a, 0xd0, 0x7d, 0xc8, 0xdb, 0xc4, 0x35, 0x27, 0x32, 0x36, + 0xbb, 0x5d, 0x13, 0x09, 0x77, 0x2d, 0x4a, 0xb8, 0x6b, 0x4d, 0x99, 0x70, 0x63, 0x81, 0xe3, 0x39, + 0x88, 0xf9, 0xd2, 0x30, 0xc3, 0x90, 0x0c, 0x47, 0xa1, 0x08, 0xcc, 0x72, 0xb8, 0x3c, 0x34, 0x5f, + 0xd6, 0x25, 0x09, 0x3d, 0x84, 0xc2, 0xb1, 0xe3, 0xd9, 0xf4, 0x58, 0xc6, 0x5e, 0x57, 0x28, 0x95, + 0x40, 0xf5, 0x84, 0x85, 0x24, 0x33, 0xc3, 0x64, 0x7b, 0xa8, 0xdd, 0x69, 0xeb, 0xd1, 0x1e, 0x92, + 0xfc, 0x8e, 0xd7, 0xa6, 0x1e, 0x3b, 0xff, 0xd0, 0x69, 0x1b, 0x9b, 0xf5, 0xd6, 0xf6, 0x2e, 0x66, + 0xfb, 0x68, 0xe5, 0xe4, 0x74, 0x43, 0x89, 0x21, 0x9b, 0xa6, 0xe3, 0xb2, 0x64, 0xe0, 0x36, 0xcc, + 0xd7, 0xdb, 0xdf, 0x51, 0xb2, 0x55, 0xe5, 0xe4, 0x74, 0xa3, 0x12, 0xb3, 0xeb, 0xde, 0x64, 0x6a, + 0xf7, 0xd9, 0x7e, 0xd5, 0xbf, 0x9b, 0x87, 0xca, 0xee, 0xc8, 0x36, 0x43, 0x22, 0xce, 0x19, 0xda, + 0x80, 0xf2, 0xc8, 0xf4, 0x4d, 0xd7, 0x25, 0xae, 0x13, 0x0c, 0x65, 0x29, 0x21, 0x49, 0x42, 0xef, + 0x7d, 0x56, 0x33, 0x36, 0x8a, 0xec, 0xec, 0x7c, 0xef, 0x5f, 0xd7, 0x33, 0x91, 0x41, 0x77, 0x61, + 0x69, 0x5f, 0x8c, 0xd6, 0x30, 0x2d, 0xbe, 0xb0, 0xf3, 0x7c, 0x61, 0x6b, 0x69, 0x0b, 0x9b, 0x1c, + 0x56, 0x4d, 0x4e, 0xb2, 0xce, 0xa5, 0xf0, 0xe2, 0x7e, 0xb2, 0x89, 0x1e, 0xc3, 0xc2, 0x90, 0x7a, + 0x4e, 0x48, 0xfd, 0xeb, 0x57, 0x21, 0x42, 0xa2, 0x7b, 0x70, 0x83, 0x2d, 0x6e, 0x34, 0x1e, 0xce, + 0xe6, 0xd7, 0x79, 0x16, 0x2f, 0x0f, 0xcd, 0x97, 0xb2, 0x43, 0xcc, 0xc8, 0xa8, 0x01, 0x79, 0xea, + 0xb3, 0x78, 0xb1, 0xc0, 0x87, 0xfb, 0xee, 0xb5, 0xc3, 0x15, 0x8d, 0x0e, 0x93, 0xc1, 0x42, 0x54, + 0xfd, 0x3a, 0x2c, 0x5e, 0x98, 0x04, 0x0b, 0x93, 0xba, 0xf5, 0xdd, 0x9e, 0xae, 0xcc, 0xa1, 0x0a, + 0x14, 0xb5, 0x4e, 0xbb, 0xdf, 0x6a, 0xef, 0xb2, 0x38, 0xaf, 0x02, 0x45, 0xdc, 0xd9, 0xde, 0x6e, + 0xd4, 0xb5, 0x67, 0x4a, 0x56, 0xad, 0x41, 0x39, 0xa1, 0x0d, 0x2d, 0x01, 0xf4, 0xfa, 0x9d, 0xae, + 0xb1, 0xd9, 0xc2, 0xbd, 0xbe, 0x88, 0x12, 0x7b, 0xfd, 0x3a, 0xee, 0x4b, 0x42, 0x46, 0xfd, 0x8f, + 0x6c, 0xb4, 0xa2, 0x32, 0x30, 0x6c, 0x5c, 0x0c, 0x0c, 0xaf, 0x18, 0xbc, 0x0c, 0x0d, 0xa7, 0x8d, + 0x38, 0x40, 0x7c, 0x0f, 0x80, 0x6f, 0x1c, 0x62, 0x1b, 0x66, 0x28, 0x17, 0xbe, 0xfa, 0x8a, 0x91, + 0xfb, 0x51, 0x45, 0x0b, 0x97, 0x24, 0xba, 0x1e, 0xa2, 0x6f, 0x40, 0xc5, 0xa2, 0xc3, 0x91, 0x4b, + 0xa4, 0xf0, 0xfc, 0xb5, 0xc2, 0xe5, 0x18, 0x5f, 0x0f, 0x93, 0xa1, 0x69, 0xee, 0x62, 0xf0, 0xfc, + 0x6b, 0x99, 0xc8, 0x32, 0x29, 0xd1, 0x68, 0x05, 0x8a, 0xbb, 0xdd, 0x66, 0xbd, 0xdf, 0x6a, 0x3f, + 0x55, 0x32, 0x08, 0xa0, 0xc0, 0x4d, 0xdd, 0x54, 0xb2, 0x2c, 0x8a, 0xd6, 0x3a, 0x3b, 0xdd, 0x6d, + 0x9d, 0x7b, 0x2c, 0xb4, 0x02, 0x4a, 0x64, 0x6c, 0x83, 0x1b, 0x52, 0x6f, 0x2a, 0x39, 0x74, 0x13, + 0x96, 0x63, 0xaa, 0x94, 0xcc, 0xa3, 0x5b, 0x80, 0x62, 0xe2, 0x54, 0x45, 0x41, 0xfd, 0x65, 0x58, + 0xd6, 0xa8, 0x17, 0x9a, 0x8e, 0x17, 0x67, 0x18, 0x8f, 0xd8, 0xa4, 0x25, 0xc9, 0x70, 0x64, 0x25, + 0xa8, 0xb1, 0x7c, 0x7e, 0xb6, 0x5e, 0x8e, 0xa1, 0xad, 0x26, 0x0f, 0x95, 0x64, 0xc3, 0x66, 0xe7, + 0x77, 0xe4, 0xd8, 0xdc, 0xb8, 0xf9, 0xc6, 0xc2, 0xf9, 0xd9, 0xfa, 0x7c, 0xb7, 0xd5, 0xc4, 0x8c, + 0x86, 0xbe, 0x08, 0x25, 0xf2, 0xd2, 0x09, 0x0d, 0x8b, 0xdd, 0x4b, 0xcc, 0x80, 0x79, 0x5c, 0x64, + 0x04, 0x8d, 0x5d, 0x43, 0x0d, 0x80, 0x2e, 0xf5, 0x43, 0xd9, 0xf3, 0x57, 0x21, 0x3f, 0xa2, 0x3e, + 0xaf, 0x5d, 0x5c, 0x5a, 0x51, 0x63, 0x70, 0xb1, 0x51, 0xb1, 0x00, 0xab, 0xbf, 0x3b, 0x0f, 0xd0, + 0x37, 0x83, 0x43, 0xa9, 0xe4, 0x09, 0x94, 0xe2, 0xea, 0xa4, 0x2c, 0x82, 0x5c, 0xb9, 0xda, 0x31, + 0x18, 0x3d, 0x8e, 0x36, 0x9b, 0xc8, 0x9d, 0x52, 0x93, 0xd8, 0xa8, 0xa3, 0xb4, 0xf4, 0xe3, 0x62, + 0x82, 0xc4, 0xae, 0x79, 0xe2, 0xfb, 0x72, 0xe5, 0xd9, 0x4f, 0xa4, 0xf1, 0x6b, 0x41, 0x18, 0x4d, + 0x46, 0xdf, 0xa9, 0x65, 0x9f, 0x99, 0x15, 0xd9, 0x9a, 0xc3, 0x53, 0x39, 0xf4, 0x21, 0x94, 0xd9, + 0xbc, 0x8d, 0x80, 0xf3, 0x64, 0xe0, 0x7d, 0xa9, 0xa9, 0x84, 0x06, 0x0c, 0xa3, 0xa9, 0x95, 0x5f, + 0x07, 0x30, 0x47, 0x23, 0xd7, 0x21, 0xb6, 0xb1, 0x37, 0xe1, 0x91, 0x76, 0x09, 0x97, 0x24, 0xa5, + 0x31, 0x61, 0xc7, 0x25, 0x62, 0x9b, 0x21, 0x8f, 0x9e, 0xaf, 0x31, 0xa0, 0x44, 0xd7, 0xc3, 0x86, + 0x02, 0x4b, 0xfe, 0xd8, 0x63, 0x06, 0x95, 0xa3, 0x53, 0xff, 0x34, 0x0b, 0xaf, 0xb5, 0x49, 0x78, + 0x4c, 0xfd, 0xc3, 0x7a, 0x18, 0x9a, 0xd6, 0xc1, 0x90, 0x78, 0x72, 0xf9, 0x12, 0x09, 0x4d, 0xe6, + 0x42, 0x42, 0xb3, 0x0a, 0x0b, 0xa6, 0xeb, 0x98, 0x01, 0x11, 0xc1, 0x5b, 0x09, 0x47, 0x4d, 0x96, + 0x76, 0xb1, 0x24, 0x8e, 0x04, 0x01, 0x11, 0x75, 0x15, 0x36, 0xf0, 0x88, 0x80, 0xbe, 0x0b, 0xb7, + 0x64, 0x98, 0x66, 0xc6, 0x5d, 0xb1, 0x84, 0x22, 0x2a, 0xd0, 0xea, 0xa9, 0x59, 0x65, 0xfa, 0xe0, + 0x64, 0x1c, 0x37, 0x25, 0x77, 0x46, 0xa1, 0x8c, 0x0a, 0x57, 0xec, 0x14, 0x56, 0xf5, 0x29, 0xdc, + 0xbe, 0x54, 0xe4, 0x73, 0xd5, 0x6d, 0xfe, 0x31, 0x0b, 0xd0, 0xea, 0xd6, 0x77, 0xa4, 0x91, 0x9a, + 0x50, 0xd8, 0x37, 0x87, 0x8e, 0x3b, 0xb9, 0xca, 0x03, 0x4e, 0xf1, 0xb5, 0xba, 0x30, 0xc7, 0x26, + 0x97, 0xc1, 0x52, 0x96, 0xe7, 0x94, 0xe3, 0x3d, 0x8f, 0x84, 0x71, 0x4e, 0xc9, 0x5b, 0x6c, 0x18, + 0xbe, 0xe9, 0xc5, 0x5b, 0x57, 0x34, 0xd8, 0x02, 0xb0, 0x90, 0xe7, 0xd8, 0x9c, 0x44, 0x6e, 0x4b, + 0x36, 0xd1, 0x16, 0xaf, 0x8e, 0x12, 0xff, 0x88, 0xd8, 0xab, 0x79, 0x6e, 0xd4, 0xeb, 0xc6, 0x83, + 0x25, 0x5c, 0xd8, 0x2e, 0x96, 0xae, 0x7e, 0xc0, 0x43, 0xa6, 0x29, 0xeb, 0x73, 0xd9, 0xe8, 0x01, + 0x2c, 0x5e, 0x98, 0xe7, 0x2b, 0xc9, 0x7c, 0xab, 0xfb, 0xfc, 0xab, 0x4a, 0x4e, 0xfe, 0xfa, 0xba, + 0x52, 0x50, 0xff, 0x76, 0x5e, 0x38, 0x1a, 0x69, 0xd5, 0xf4, 0x57, 0x81, 0x22, 0xdf, 0xdd, 0x16, + 0x75, 0xa5, 0x03, 0x78, 0xfb, 0x6a, 0xff, 0xc3, 0x72, 0x3a, 0x0e, 0xc7, 0xb1, 0x20, 0x5a, 0x87, + 0xb2, 0xd8, 0xc5, 0x06, 0x3b, 0x70, 0xdc, 0xac, 0x8b, 0x18, 0x04, 0x89, 0x49, 0xa2, 0xbb, 0xb0, + 0xc4, 0x8b, 0x3f, 0xc1, 0x01, 0xb1, 0x05, 0x26, 0xc7, 0x31, 0x8b, 0x31, 0x95, 0xc3, 0x76, 0xa0, + 0x22, 0x09, 0x06, 0x8f, 0xe7, 0xf3, 0x7c, 0x40, 0xf7, 0xae, 0x1b, 0x90, 0x10, 0xe1, 0x61, 0x7e, + 0x79, 0x34, 0x6d, 0xa8, 0x3f, 0x0f, 0xc5, 0x68, 0xb0, 0x68, 0x15, 0xe6, 0xfb, 0x5a, 0x57, 0x99, + 0xab, 0x2e, 0x9f, 0x9c, 0x6e, 0x94, 0x23, 0x72, 0x5f, 0xeb, 0x32, 0xce, 0x6e, 0xb3, 0xab, 0x64, + 0x2e, 0x72, 0x76, 0x9b, 0x5d, 0x54, 0x85, 0x5c, 0x4f, 0xeb, 0x77, 0xa3, 0xf8, 0x2c, 0x62, 0x31, + 0x5a, 0x35, 0xc7, 0xe2, 0x33, 0x75, 0x1f, 0xca, 0x89, 0xde, 0xd1, 0x9b, 0xb0, 0xd0, 0x6a, 0x3f, + 0xc5, 0x7a, 0xaf, 0xa7, 0xcc, 0x55, 0x6f, 0x9d, 0x9c, 0x6e, 0xa0, 0x04, 0xb7, 0xe5, 0x0d, 0xd8, + 0xda, 0xa1, 0xd7, 0x21, 0xb7, 0xd5, 0x61, 0xf7, 0xbe, 0x48, 0x2e, 0x12, 0x88, 0x2d, 0x1a, 0x84, + 0xd5, 0x9b, 0x32, 0xf0, 0x4b, 0x2a, 0x56, 0x7f, 0x2f, 0x03, 0x05, 0x71, 0xd0, 0x52, 0x17, 0xb1, + 0x0e, 0x0b, 0x51, 0x09, 0x41, 0x24, 0x7e, 0x6f, 0x5f, 0x9e, 0xa4, 0xd5, 0x64, 0x4e, 0x25, 0xb6, + 0x66, 0x24, 0x57, 0x7d, 0x1f, 0x2a, 0x49, 0xc6, 0xe7, 0xda, 0x98, 0xdf, 0x85, 0x32, 0xdb, 0xfb, + 0x51, 0xb2, 0xf6, 0x08, 0x0a, 0xc2, 0x59, 0xc4, 0xf7, 0xd0, 0xe5, 0x19, 0xa3, 0x44, 0xa2, 0x27, + 0xb0, 0x20, 0xb2, 0xcc, 0xa8, 0x72, 0xbc, 0x76, 0xf5, 0x09, 0xc3, 0x11, 0x5c, 0xfd, 0x10, 0x72, + 0x5d, 0x42, 0x7c, 0x66, 0x7b, 0x8f, 0xda, 0x64, 0x7a, 0x75, 0xcb, 0x04, 0xd9, 0x26, 0xad, 0x26, + 0x4b, 0x90, 0x6d, 0xd2, 0xb2, 0xe3, 0xda, 0x58, 0x36, 0x51, 0x1b, 0xeb, 0x43, 0xe5, 0x05, 0x71, + 0x06, 0x07, 0x21, 0xb1, 0xb9, 0xa2, 0x77, 0x21, 0x37, 0x22, 0xf1, 0xe0, 0x57, 0x53, 0x37, 0x1f, + 0x21, 0x3e, 0xe6, 0x28, 0xe6, 0x63, 0x8e, 0xb9, 0xb4, 0x7c, 0xee, 0x90, 0x2d, 0xf5, 0x1f, 0xb2, + 0xb0, 0xd4, 0x0a, 0x82, 0xb1, 0xe9, 0x59, 0x51, 0x54, 0xf7, 0xcd, 0x8b, 0x51, 0x5d, 0xea, 0xbb, + 0xd0, 0x45, 0x91, 0x8b, 0x25, 0x3f, 0x79, 0xb3, 0x66, 0xe3, 0x9b, 0x55, 0xfd, 0x71, 0x26, 0xaa, + 0xeb, 0xdd, 0x4d, 0xb8, 0x02, 0x91, 0x23, 0x26, 0x35, 0x91, 0x5d, 0xef, 0xd0, 0xa3, 0xc7, 0x1e, + 0x7a, 0x03, 0xf2, 0x58, 0x6f, 0xeb, 0x2f, 0x94, 0x8c, 0xd8, 0x9e, 0x17, 0x40, 0x98, 0x78, 0xe4, + 0x98, 0x69, 0xea, 0xea, 0xed, 0x26, 0x8b, 0xc2, 0xb2, 0x29, 0x9a, 0xba, 0xc4, 0xb3, 0x1d, 0x6f, + 0x80, 0xde, 0x84, 0x42, 0xab, 0xd7, 0xdb, 0xe5, 0x29, 0xe4, 0x6b, 0x27, 0xa7, 0x1b, 0x37, 0x2f, + 0xa0, 0x78, 0x4d, 0xd7, 0x66, 0x20, 0x96, 0x02, 0xb1, 0xf8, 0x2c, 0x05, 0xc4, 0x62, 0x6b, 0x01, + 0xc2, 0x9d, 0x7e, 0xbd, 0xaf, 0x2b, 0xf9, 0x14, 0x10, 0xa6, 0xec, 0xaf, 0x3c, 0x6e, 0xff, 0x92, + 0x05, 0xa5, 0x6e, 0x59, 0x64, 0x14, 0x32, 0xbe, 0xcc, 0x3a, 0xfb, 0x50, 0x1c, 0xb1, 0x5f, 0x0e, + 0x89, 0x22, 0xa8, 0x27, 0xa9, 0x2f, 0x9b, 0x33, 0x72, 0x35, 0x4c, 0x5d, 0x52, 0xb7, 0x87, 0x4e, + 0x10, 0x38, 0xd4, 0x13, 0x34, 0x1c, 0x6b, 0xaa, 0xfe, 0x67, 0x06, 0x6e, 0xa6, 0x20, 0xd0, 0x03, + 0xc8, 0xf9, 0xd4, 0x8d, 0xd6, 0xf0, 0xce, 0x65, 0x25, 0x5b, 0x26, 0x8a, 0x39, 0x12, 0xad, 0x01, + 0x98, 0xe3, 0x90, 0x9a, 0xbc, 0x7f, 0xbe, 0x7a, 0x45, 0x9c, 0xa0, 0xa0, 0x17, 0x50, 0x08, 0x88, + 0xe5, 0x93, 0x28, 0xce, 0xfe, 0xf0, 0xff, 0x3a, 0xfa, 0x5a, 0x8f, 0xab, 0xc1, 0x52, 0x5d, 0xb5, + 0x06, 0x05, 0x41, 0x61, 0xdb, 0xde, 0x36, 0x43, 0x53, 0x16, 0xf4, 0xf9, 0x6f, 0xb6, 0x9b, 0x4c, + 0x77, 0x10, 0xed, 0x26, 0xd3, 0x1d, 0xa8, 0x7f, 0x93, 0x05, 0xd0, 0x5f, 0x86, 0xc4, 0xf7, 0x4c, + 0x57, 0xab, 0x23, 0x3d, 0x71, 0x33, 0x88, 0xd9, 0x7e, 0x39, 0xf5, 0x95, 0x22, 0x96, 0xa8, 0x69, + 0xf5, 0x94, 0xbb, 0xe1, 0x36, 0xcc, 0x8f, 0x7d, 0xf9, 0x58, 0x2d, 0x62, 0xe4, 0x5d, 0xbc, 0x8d, + 0x19, 0x0d, 0xe9, 0x53, 0xb7, 0x35, 0x7f, 0xf9, 0x93, 0x74, 0xa2, 0x83, 0x54, 0xd7, 0xc5, 0x4e, + 0xbe, 0x65, 0x1a, 0x16, 0x91, 0xb7, 0x4a, 0x45, 0x9c, 0x7c, 0xad, 0xae, 0x11, 0x3f, 0xc4, 0x05, + 0xcb, 0x64, 0xff, 0x7f, 0x22, 0xff, 0xf6, 0x2e, 0xc0, 0x74, 0x6a, 0x68, 0x0d, 0xf2, 0xda, 0x66, + 0xaf, 0xb7, 0xad, 0xcc, 0x09, 0x07, 0x3e, 0x65, 0x71, 0xb2, 0xfa, 0x97, 0x59, 0x28, 0x6a, 0x75, + 0x79, 0xe5, 0x6a, 0xa0, 0x70, 0xaf, 0xc4, 0x9f, 0x41, 0xc8, 0xcb, 0x91, 0xe3, 0x4f, 0xa4, 0x63, + 0xb9, 0x22, 0xe1, 0x5d, 0x62, 0x22, 0x6c, 0xd4, 0x3a, 0x17, 0x40, 0x18, 0x2a, 0x44, 0x1a, 0xc1, + 0xb0, 0xcc, 0xc8, 0xc7, 0xaf, 0x5d, 0x6d, 0x2c, 0x91, 0xba, 0x4c, 0xdb, 0x01, 0x2e, 0x47, 0x4a, + 0x34, 0x33, 0x40, 0xef, 0xc1, 0x72, 0xe0, 0x0c, 0x3c, 0xc7, 0x1b, 0x18, 0x91, 0xf1, 0xf8, 0x9b, + 0x4c, 0xe3, 0xc6, 0xf9, 0xd9, 0xfa, 0x62, 0x4f, 0xb0, 0xa4, 0x0d, 0x17, 0x25, 0x52, 0xe3, 0xa6, + 0x44, 0x5f, 0x87, 0xa5, 0x84, 0x28, 0xb3, 0xa2, 0x30, 0xbb, 0x72, 0x7e, 0xb6, 0x5e, 0x89, 0x25, + 0x9f, 0x91, 0x09, 0xae, 0xc4, 0x82, 0xcf, 0x08, 0xaf, 0xcd, 0xec, 0x53, 0xdf, 0x22, 0x86, 0xcf, + 0xcf, 0x34, 0xbf, 0xdd, 0x73, 0xb8, 0xcc, 0x69, 0xe2, 0x98, 0xab, 0xcf, 0xe1, 0x66, 0xc7, 0xb7, + 0x0e, 0x48, 0x10, 0x0a, 0x53, 0x48, 0x2b, 0x7e, 0x08, 0x77, 0x42, 0x33, 0x38, 0x34, 0x0e, 0x9c, + 0x20, 0xa4, 0xfe, 0xc4, 0xf0, 0x49, 0x48, 0x3c, 0xc6, 0x37, 0xf8, 0x43, 0xae, 0x2c, 0x08, 0xde, + 0x66, 0x98, 0x2d, 0x01, 0xc1, 0x11, 0x62, 0x9b, 0x01, 0xd4, 0x16, 0x54, 0x58, 0x0a, 0x23, 0x8b, + 0x6a, 0x6c, 0xf6, 0xe0, 0xd2, 0x81, 0xf1, 0x99, 0xaf, 0xa9, 0x92, 0x4b, 0x07, 0xe2, 0xa7, 0xfa, + 0x6d, 0x50, 0x9a, 0x4e, 0x30, 0x32, 0x43, 0xeb, 0x20, 0xaa, 0x74, 0xa2, 0x26, 0x28, 0x07, 0xc4, + 0xf4, 0xc3, 0x3d, 0x62, 0x86, 0xc6, 0x88, 0xf8, 0x0e, 0xb5, 0xaf, 0x5f, 0xe5, 0xe5, 0x58, 0xa4, + 0xcb, 0x25, 0xd4, 0xff, 0xce, 0x00, 0x60, 0x73, 0x3f, 0x8a, 0xd6, 0xbe, 0x02, 0x37, 0x02, 0xcf, + 0x1c, 0x05, 0x07, 0x34, 0x34, 0x1c, 0x2f, 0x24, 0xfe, 0x91, 0xe9, 0xca, 0xe2, 0x8e, 0x12, 0x31, + 0x5a, 0x92, 0x8e, 0xde, 0x05, 0x74, 0x48, 0xc8, 0xc8, 0xa0, 0xae, 0x6d, 0x44, 0x4c, 0xf1, 0xcc, + 0x9c, 0xc3, 0x0a, 0xe3, 0x74, 0x5c, 0xbb, 0x17, 0xd1, 0x51, 0x03, 0xd6, 0xd8, 0xf4, 0x89, 0x17, + 0xfa, 0x0e, 0x09, 0x8c, 0x7d, 0xea, 0x1b, 0x81, 0x4b, 0x8f, 0x8d, 0x7d, 0xea, 0xba, 0xf4, 0x98, + 0xf8, 0x51, 0xdd, 0xac, 0xea, 0xd2, 0x81, 0x2e, 0x40, 0x9b, 0xd4, 0xef, 0xb9, 0xf4, 0x78, 0x33, + 0x42, 0xb0, 0x90, 0x6e, 0x3a, 0xe7, 0xd0, 0xb1, 0x0e, 0xa3, 0x90, 0x2e, 0xa6, 0xf6, 0x1d, 0xeb, + 0x10, 0xbd, 0x09, 0x8b, 0xc4, 0x25, 0xbc, 0x7c, 0x22, 0x50, 0x79, 0x8e, 0xaa, 0x44, 0x44, 0x06, + 0x52, 0x3f, 0x02, 0x45, 0xf7, 0x2c, 0x7f, 0x32, 0x4a, 0xac, 0xf9, 0xbb, 0x80, 0x98, 0x93, 0x34, + 0x5c, 0x6a, 0x1d, 0x1a, 0x43, 0xd3, 0x33, 0x07, 0x6c, 0x5c, 0xe2, 0xf5, 0x4f, 0x61, 0x9c, 0x6d, + 0x6a, 0x1d, 0xee, 0x48, 0xba, 0xfa, 0x1e, 0x40, 0x6f, 0xe4, 0x13, 0xd3, 0xee, 0xb0, 0x68, 0x82, + 0x99, 0x8e, 0xb7, 0x0c, 0x5b, 0xbe, 0x9e, 0x52, 0x5f, 0x1e, 0x75, 0x45, 0x30, 0x9a, 0x31, 0x5d, + 0xfd, 0x59, 0xb8, 0xd9, 0x75, 0x4d, 0x8b, 0x7f, 0x49, 0xd0, 0x8d, 0x9f, 0xb3, 0xd0, 0x13, 0x28, + 0x08, 0xa8, 0x5c, 0xc9, 0xd4, 0xe3, 0x36, 0xed, 0x73, 0x6b, 0x0e, 0x4b, 0x7c, 0xa3, 0x02, 0x30, + 0xd5, 0xa3, 0xfe, 0x79, 0x06, 0x4a, 0xb1, 0x7e, 0xb4, 0x21, 0x5e, 0x69, 0x42, 0xdf, 0x74, 0x3c, + 0x99, 0xf1, 0x97, 0x70, 0x92, 0x84, 0x5a, 0x50, 0x1e, 0xc5, 0xd2, 0x57, 0xc6, 0x73, 0x29, 0xa3, + 0xc6, 0x49, 0x59, 0xf4, 0x3e, 0x94, 0xa2, 0xe7, 0xea, 0xc8, 0xc3, 0x5e, 0xfd, 0xba, 0x3d, 0x85, + 0xab, 0xdf, 0x04, 0xf8, 0x16, 0x75, 0xbc, 0x3e, 0x3d, 0x24, 0x1e, 0x7f, 0x7e, 0x65, 0xf9, 0x22, + 0x89, 0xac, 0x28, 0x5b, 0xbc, 0x0c, 0x20, 0x96, 0x20, 0x7e, 0x85, 0x14, 0x4d, 0xf5, 0xaf, 0xb3, + 0x50, 0xc0, 0x94, 0x86, 0x5a, 0x1d, 0x6d, 0x40, 0x41, 0xfa, 0x09, 0x7e, 0xff, 0x34, 0x4a, 0xe7, + 0x67, 0xeb, 0x79, 0xe1, 0x20, 0xf2, 0x16, 0xf7, 0x0c, 0x09, 0x0f, 0x9e, 0xbd, 0xcc, 0x83, 0xa3, + 0x07, 0x50, 0x91, 0x20, 0xe3, 0xc0, 0x0c, 0x0e, 0x44, 0xf2, 0xd6, 0x58, 0x3a, 0x3f, 0x5b, 0x07, + 0x81, 0xdc, 0x32, 0x83, 0x03, 0x0c, 0x02, 0xcd, 0x7e, 0x23, 0x1d, 0xca, 0x1f, 0x53, 0xc7, 0x33, + 0x42, 0x3e, 0x09, 0x59, 0x68, 0x4c, 0x5d, 0xc7, 0xe9, 0x54, 0xe5, 0xb7, 0x08, 0xf0, 0xf1, 0x74, + 0xf2, 0x3a, 0x2c, 0xfa, 0x94, 0x86, 0xc2, 0x6d, 0x39, 0xd4, 0x93, 0x35, 0x8c, 0x8d, 0xd4, 0xd2, + 0x36, 0xa5, 0x21, 0x96, 0x38, 0x5c, 0xf1, 0x13, 0x2d, 0xf4, 0x00, 0x56, 0x5c, 0x33, 0x08, 0x0d, + 0xee, 0xef, 0xec, 0xa9, 0xb6, 0x02, 0x3f, 0x6a, 0x88, 0xf1, 0x36, 0x39, 0x2b, 0x92, 0x50, 0xff, + 0x29, 0x03, 0x65, 0x36, 0x19, 0x67, 0xdf, 0xb1, 0x58, 0x90, 0xf7, 0xf9, 0x63, 0x8f, 0xdb, 0x30, + 0x6f, 0x05, 0xbe, 0x34, 0x2a, 0xbf, 0x7c, 0xb5, 0x1e, 0xc6, 0x8c, 0x86, 0x3e, 0x82, 0x82, 0xac, + 0xa5, 0x88, 0xb0, 0x43, 0xbd, 0x3e, 0x1c, 0x95, 0xb6, 0x91, 0x72, 0x7c, 0x2f, 0x4f, 0x47, 0x27, + 0x2e, 0x01, 0x9c, 0x24, 0xa1, 0x5b, 0x90, 0xb5, 0x84, 0xb9, 0xe4, 0xc7, 0x2e, 0x5a, 0x1b, 0x67, + 0x2d, 0x4f, 0xfd, 0x41, 0x06, 0x16, 0xa7, 0x07, 0x9e, 0xed, 0x80, 0x3b, 0x50, 0x0a, 0xc6, 0x7b, + 0xc1, 0x24, 0x08, 0xc9, 0x30, 0x7a, 0x5a, 0x8e, 0x09, 0xa8, 0x05, 0x25, 0xd3, 0x1d, 0x50, 0xdf, + 0x09, 0x0f, 0x86, 0x32, 0x4b, 0x4d, 0x0f, 0x15, 0x92, 0x3a, 0x6b, 0xf5, 0x48, 0x04, 0x4f, 0xa5, + 0xa3, 0x7b, 0x5f, 0x7c, 0x7f, 0xc0, 0xef, 0xfd, 0x37, 0xa0, 0xe2, 0x9a, 0x43, 0x5e, 0x5c, 0x0a, + 0x9d, 0xa1, 0x98, 0x47, 0x0e, 0x97, 0x25, 0xad, 0xef, 0x0c, 0x89, 0xaa, 0x42, 0x29, 0x56, 0x86, + 0x96, 0xa1, 0x5c, 0xd7, 0x7b, 0xc6, 0xc3, 0x47, 0x4f, 0x8c, 0xa7, 0xda, 0x8e, 0x32, 0x27, 0x63, + 0xd3, 0xbf, 0xc8, 0xc0, 0xa2, 0x74, 0x47, 0x32, 0xde, 0x7f, 0x13, 0x16, 0x7c, 0x73, 0x3f, 0x8c, + 0x32, 0x92, 0x9c, 0xd8, 0xd5, 0xcc, 0xc3, 0xb3, 0x8c, 0x84, 0xb1, 0xd2, 0x33, 0x92, 0xc4, 0xc7, + 0x0e, 0xf3, 0x57, 0x7e, 0xec, 0x90, 0xfb, 0xa9, 0x7c, 0xec, 0xa0, 0xfe, 0x0a, 0xc0, 0xa6, 0xe3, + 0x92, 0xbe, 0xa8, 0x43, 0xa5, 0xe5, 0x97, 0x2c, 0x86, 0x93, 0x75, 0xce, 0x28, 0x86, 0x6b, 0x35, + 0x31, 0xa3, 0x31, 0xd6, 0xc0, 0xb1, 0xe5, 0x61, 0xe4, 0xac, 0xa7, 0x8c, 0x35, 0x70, 0xec, 0xf8, + 0x55, 0x2e, 0x77, 0xdd, 0xab, 0xdc, 0x69, 0x06, 0x96, 0x65, 0xec, 0x1a, 0xbb, 0xdf, 0x2f, 0x43, + 0x49, 0x84, 0xb1, 0xd3, 0x84, 0x8e, 0x3f, 0xf0, 0x0b, 0x5c, 0xab, 0x89, 0x8b, 0x82, 0xdd, 0xb2, + 0xd1, 0x3a, 0x94, 0x25, 0x34, 0xf1, 0x61, 0x14, 0x08, 0x52, 0x9b, 0x0d, 0xff, 0xab, 0x90, 0xdb, + 0x77, 0x5c, 0x22, 0x37, 0x7a, 0xaa, 0x03, 0x98, 0x1a, 0x60, 0x6b, 0x0e, 0x73, 0x74, 0xa3, 0x18, + 0x15, 0xea, 0xf8, 0xf8, 0x64, 0xda, 0x99, 0x1c, 0x9f, 0xc8, 0x40, 0x67, 0xc6, 0x27, 0x70, 0x6c, + 0x7c, 0x82, 0x2d, 0xc6, 0x27, 0xa1, 0xc9, 0xf1, 0x09, 0xd2, 0x4f, 0x65, 0x7c, 0xdb, 0x70, 0xab, + 0xe1, 0x9a, 0xd6, 0xa1, 0xeb, 0x04, 0x21, 0xb1, 0x93, 0x1e, 0xe3, 0x11, 0x14, 0x2e, 0x04, 0x9d, + 0x57, 0x55, 0x34, 0x25, 0x52, 0xfd, 0xf7, 0x0c, 0x54, 0xb6, 0x88, 0xe9, 0x86, 0x07, 0xd3, 0xb2, + 0x51, 0x48, 0x82, 0x50, 0x5e, 0x56, 0xfc, 0x37, 0xfa, 0x1a, 0x14, 0xe3, 0x98, 0xe4, 0xda, 0xb7, + 0xb9, 0x18, 0x8a, 0x1e, 0xc3, 0x02, 0x3b, 0x63, 0x74, 0x1c, 0x25, 0x3b, 0x57, 0x3d, 0xfb, 0x48, + 0x24, 0xbb, 0x64, 0x7c, 0xc2, 0x83, 0x10, 0xbe, 0x95, 0xf2, 0x38, 0x6a, 0xa2, 0xff, 0x0f, 0x15, + 0xfe, 0x6a, 0x11, 0xc5, 0x5c, 0xf9, 0xeb, 0x74, 0x96, 0xc5, 0xc3, 0xa3, 0x88, 0xb7, 0xfe, 0x38, + 0x0b, 0x2b, 0x3b, 0xe6, 0x64, 0x8f, 0x48, 0xb7, 0x41, 0x6c, 0x4c, 0x2c, 0xea, 0xdb, 0xa8, 0x9b, + 0x74, 0x37, 0x57, 0xbc, 0x63, 0xa6, 0x09, 0xa7, 0x7b, 0x9d, 0x28, 0x01, 0xcb, 0x26, 0x12, 0xb0, + 0x15, 0xc8, 0x7b, 0xd4, 0xb3, 0x88, 0xf4, 0x45, 0xa2, 0xa1, 0xfe, 0x76, 0x26, 0xe9, 0x6b, 0xaa, + 0xf1, 0x1b, 0x23, 0xaf, 0x40, 0xb5, 0x69, 0x18, 0x77, 0x87, 0x3e, 0x82, 0x6a, 0x4f, 0xd7, 0xb0, + 0xde, 0x6f, 0x74, 0xbe, 0x6d, 0xf4, 0xea, 0xdb, 0xbd, 0xfa, 0xa3, 0x07, 0x46, 0xb7, 0xb3, 0xfd, + 0x9d, 0x87, 0x8f, 0x1f, 0x7c, 0x4d, 0xc9, 0x54, 0x37, 0x4e, 0x4e, 0x37, 0xee, 0xb4, 0xeb, 0xda, + 0xb6, 0x38, 0x32, 0x7b, 0xf4, 0x65, 0xcf, 0x74, 0x03, 0xf3, 0xd1, 0x83, 0x2e, 0x75, 0x27, 0x0c, + 0x83, 0xbe, 0x02, 0x68, 0x53, 0xc7, 0x6d, 0xbd, 0x6f, 0x44, 0x0e, 0x4d, 0x6b, 0x68, 0x4a, 0x56, + 0xa4, 0x35, 0x9b, 0xc4, 0xf7, 0x48, 0x58, 0xd7, 0x7b, 0x0f, 0x1f, 0x3d, 0xd1, 0x1a, 0x1a, 0x3b, + 0x04, 0x95, 0xe4, 0xed, 0x96, 0xbc, 0xb4, 0x33, 0x97, 0x5e, 0xda, 0xd3, 0xbb, 0x3f, 0x7b, 0xc9, + 0xdd, 0xbf, 0x09, 0x2b, 0x96, 0x4f, 0x83, 0xc0, 0x60, 0xb9, 0x02, 0xb1, 0x67, 0xb2, 0x91, 0x2f, + 0x9c, 0x9f, 0xad, 0xdf, 0xd0, 0x18, 0xbf, 0xc7, 0xd9, 0x52, 0xfd, 0x0d, 0x2b, 0x41, 0xe2, 0x3d, + 0xa9, 0xbf, 0x3f, 0xcf, 0xc2, 0x2e, 0xe7, 0xc8, 0x71, 0xc9, 0x80, 0x04, 0xe8, 0x39, 0x2c, 0x5b, + 0x3e, 0xb1, 0x59, 0x12, 0x60, 0xba, 0xc9, 0xcf, 0x71, 0xff, 0x5f, 0x6a, 0x04, 0x14, 0x0b, 0xd6, + 0xb4, 0x58, 0xaa, 0x37, 0x22, 0x16, 0x5e, 0xb2, 0x2e, 0xb4, 0xd1, 0xc7, 0xb0, 0x1c, 0x10, 0xd7, + 0xf1, 0xc6, 0x2f, 0x0d, 0x8b, 0x7a, 0x21, 0x79, 0x19, 0xbd, 0xad, 0x5d, 0xa7, 0xb7, 0xa7, 0x6f, + 0x33, 0x29, 0x4d, 0x08, 0x35, 0xd0, 0xf9, 0xd9, 0xfa, 0xd2, 0x45, 0x1a, 0x5e, 0x92, 0x9a, 0x65, + 0xbb, 0xda, 0x86, 0xa5, 0x8b, 0xa3, 0x41, 0x2b, 0xd2, 0x53, 0x70, 0x87, 0x13, 0x79, 0x02, 0x74, + 0x07, 0x8a, 0x3e, 0x19, 0x38, 0x41, 0xe8, 0x0b, 0x33, 0x33, 0x4e, 0x4c, 0x61, 0x7e, 0x42, 0x7c, + 0x4b, 0x55, 0xfd, 0x25, 0x98, 0xe9, 0x91, 0x1d, 0x2d, 0xdb, 0x09, 0xcc, 0x3d, 0xa9, 0xb2, 0x88, + 0xa3, 0x26, 0xdb, 0xb1, 0xe3, 0x20, 0x0e, 0xeb, 0xf8, 0x6f, 0x46, 0xe3, 0xf1, 0x87, 0xfc, 0xb2, + 0x8c, 0x47, 0x18, 0xd1, 0x27, 0xaa, 0xb9, 0xc4, 0x27, 0xaa, 0x2b, 0x90, 0x77, 0xc9, 0x11, 0x71, + 0xc5, 0xcd, 0x8f, 0x45, 0xe3, 0xde, 0x03, 0xa8, 0x44, 0xdf, 0x42, 0xf2, 0x6f, 0x30, 0x8a, 0x90, + 0xeb, 0xd7, 0x7b, 0xcf, 0x94, 0x39, 0x04, 0x50, 0x10, 0x3b, 0x59, 0xbc, 0xfb, 0x69, 0x9d, 0xf6, + 0x66, 0xeb, 0xa9, 0x92, 0xbd, 0xf7, 0x3b, 0x39, 0x28, 0xc5, 0x2f, 0x4f, 0xec, 0xa6, 0x69, 0xeb, + 0x2f, 0xa2, 0xa3, 0x10, 0xd3, 0xdb, 0xe4, 0x18, 0xbd, 0x31, 0xad, 0x59, 0x7d, 0x24, 0x9e, 0xda, + 0x63, 0x76, 0x54, 0xaf, 0x7a, 0x0b, 0x8a, 0xf5, 0x5e, 0xaf, 0xf5, 0xb4, 0xad, 0x37, 0x95, 0x4f, + 0x33, 0xd5, 0x2f, 0x9c, 0x9c, 0x6e, 0xdc, 0x88, 0x41, 0xf5, 0x40, 0x6c, 0x3e, 0x8e, 0xd2, 0x34, + 0xbd, 0xdb, 0xd7, 0x9b, 0xca, 0x27, 0xd9, 0x59, 0x14, 0xaf, 0xc1, 0xf0, 0x8f, 0x80, 0x4a, 0x5d, + 0xac, 0x77, 0xeb, 0x98, 0x75, 0xf8, 0x69, 0x56, 0x94, 0xd2, 0xa6, 0x3d, 0xfa, 0x64, 0x64, 0xfa, + 0xac, 0xcf, 0xb5, 0xe8, 0xab, 0xba, 0x4f, 0xe6, 0xc5, 0x87, 0x22, 0xd3, 0x67, 0x34, 0x62, 0xda, + 0x13, 0xd6, 0x1b, 0x7f, 0xbf, 0xe4, 0x6a, 0xe6, 0x67, 0x7a, 0xeb, 0x31, 0x4f, 0xc5, 0xb4, 0xa8, + 0xb0, 0x80, 0x77, 0xdb, 0x6d, 0x06, 0xfa, 0x24, 0x37, 0x33, 0x3b, 0x3c, 0xf6, 0x58, 0x7e, 0x8d, + 0xee, 0x42, 0x31, 0x7a, 0xde, 0x54, 0x3e, 0xcd, 0xcd, 0x0c, 0x48, 0x8b, 0xde, 0x66, 0x79, 0x87, + 0x5b, 0xbb, 0x7d, 0xfe, 0xd1, 0xdf, 0x27, 0xf9, 0xd9, 0x0e, 0x0f, 0xc6, 0xa1, 0x4d, 0x8f, 0x3d, + 0x76, 0x66, 0x65, 0xd5, 0xee, 0xd3, 0xbc, 0xf0, 0x05, 0x31, 0x46, 0x96, 0xec, 0xde, 0x82, 0x22, + 0xd6, 0xbf, 0x25, 0xbe, 0x0f, 0xfc, 0xa4, 0x30, 0xa3, 0x07, 0x93, 0x8f, 0x89, 0xc5, 0x7a, 0xdb, + 0x80, 0x02, 0xd6, 0x77, 0x3a, 0xcf, 0x75, 0xe5, 0x0f, 0x0a, 0x33, 0x7a, 0x30, 0x19, 0x52, 0xfe, + 0x95, 0x54, 0xb1, 0x83, 0xbb, 0x5b, 0x75, 0xbe, 0x28, 0xb3, 0x7a, 0x3a, 0xfe, 0xe8, 0xc0, 0xf4, + 0x88, 0x3d, 0xfd, 0x9e, 0x26, 0x66, 0xdd, 0xfb, 0x39, 0x28, 0x46, 0x91, 0x2e, 0x5a, 0x83, 0xc2, + 0x8b, 0x0e, 0x7e, 0xa6, 0x63, 0x65, 0x4e, 0x58, 0x39, 0xe2, 0xbc, 0x10, 0x39, 0xca, 0x06, 0x2c, + 0xec, 0xd4, 0xdb, 0xf5, 0xa7, 0x3a, 0x8e, 0x4a, 0xee, 0x11, 0x40, 0x86, 0x6b, 0x55, 0x45, 0x76, + 0x10, 0xeb, 0x6c, 0xac, 0x7e, 0xff, 0x47, 0x6b, 0x73, 0x3f, 0xfc, 0xd1, 0xda, 0xdc, 0x27, 0xe7, + 0x6b, 0x99, 0xef, 0x9f, 0xaf, 0x65, 0xfe, 0xfe, 0x7c, 0x2d, 0xf3, 0x6f, 0xe7, 0x6b, 0x99, 0xbd, + 0x02, 0xbf, 0x54, 0x1e, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x92, 0xe0, 0x5d, 0x4a, 0x5c, + 0x32, 0x00, 0x00, } diff --git a/api/types.proto b/api/types.proto index a7c58264be..73c202f0be 100644 --- a/api/types.proto +++ b/api/types.proto @@ -1035,6 +1035,7 @@ message MaybeEncryptedRecord { enum Algorithm { NONE = 0 [(gogoproto.enumvalue_customname) = "NotEncrypted"]; SECRETBOX_SALSA20_POLY1305 = 1 [(gogoproto.enumvalue_customname) = "NACLSecretboxSalsa20Poly1305"]; + FERNET_AES_128_CBC = 2 [(gogoproto.enumvalue_customname) = "FernetAES128CBC"]; } Algorithm algorithm = 1; diff --git a/manager/encryption/fernet.go b/manager/encryption/fernet.go new file mode 100644 index 0000000000..d4d048b2e7 --- /dev/null +++ b/manager/encryption/fernet.go @@ -0,0 +1,54 @@ +package encryption + +import ( + "fmt" + + "github.com/docker/swarmkit/api" + + "github.com/fernet/fernet-go" +) + +// Fernet wraps the `fernet` library as an implementation of encrypter/decrypter. +type Fernet struct { + key fernet.Key +} + +// NewFernet returns a new Fernet encrypter/decrypter with the given key +func NewFernet(key []byte) Fernet { + frnt := Fernet{} + copy(frnt.key[:], key) + return frnt +} + +// Algorithm returns the type of algorithm this is (Fernet, which uses AES128-CBC) +func (f Fernet) Algorithm() api.MaybeEncryptedRecord_Algorithm { + return api.MaybeEncryptedRecord_FernetAES128CBC +} + +// Encrypt encrypts some bytes and returns an encrypted record +func (f Fernet) Encrypt(data []byte) (*api.MaybeEncryptedRecord, error) { + out, err := fernet.EncryptAndSign(data, &f.key) + if err != nil { + return nil, err + } + // fernet generates its own IVs, so nonce is empty + return &api.MaybeEncryptedRecord{ + Algorithm: f.Algorithm(), + Data: out, + }, nil +} + +// Decrypt decrypts a MaybeEncryptedRecord and returns some bytes +func (f Fernet) Decrypt(record api.MaybeEncryptedRecord) ([]byte, error) { + if record.Algorithm != f.Algorithm() { + return nil, fmt.Errorf("record is not a Fernet message") + } + + // -1 skips the TTL check, since we don't care about message expiry + out := fernet.VerifyAndDecrypt(record.Data, -1, []*fernet.Key{&f.key}) + // VerifyandDecrypt returns a nil message if it can't be verified and decrypted + if out == nil { + return nil, fmt.Errorf("decryption error using Fernet") + } + return out, nil +} diff --git a/manager/encryption/fernet_test.go b/manager/encryption/fernet_test.go new file mode 100644 index 0000000000..be29a6e194 --- /dev/null +++ b/manager/encryption/fernet_test.go @@ -0,0 +1,77 @@ +package encryption + +import ( + cryptorand "crypto/rand" + "io" + "testing" + + "github.com/docker/swarmkit/api" + "github.com/stretchr/testify/require" +) + +// Using the same key to encrypt the same message, this encrypter produces two +// different ciphertexts because the underlying algorithm uses different IVs. +// Both of these can be decrypted into the same data though. +func TestFernet(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + keyCopy := make([]byte, 32) + copy(key, keyCopy) + + crypter1 := NewFernet(key) + crypter2 := NewFernet(keyCopy) + data := []byte("Hello again world") + + er1, err := crypter1.Encrypt(data) + require.NoError(t, err) + + er2, err := crypter2.Encrypt(data) + require.NoError(t, err) + + require.NotEqual(t, er1.Data, er2.Data) + require.Empty(t, er1.Nonce) + require.Empty(t, er2.Nonce) + + // it doesn't matter what the nonce is, it's ignored + _, err = io.ReadFull(cryptorand.Reader, er1.Nonce) + require.NoError(t, err) + + // both crypters can decrypt the other's text + for i, decrypter := range []Decrypter{crypter1, crypter2} { + for j, record := range []*api.MaybeEncryptedRecord{er1, er2} { + result, err := decrypter.Decrypt(*record) + require.NoError(t, err, "error decrypting ciphertext produced by cryptor %d using cryptor %d", j+1, i+1) + require.Equal(t, data, result) + } + } +} + +func TestFernetInvalidAlgorithm(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + + crypter := NewFernet(key) + er, err := crypter.Encrypt([]byte("Hello again world")) + require.NoError(t, err) + er.Algorithm = api.MaybeEncryptedRecord_NotEncrypted + + _, err = crypter.Decrypt(*er) + require.Error(t, err) + require.Contains(t, err.Error(), "not a Fernet message") +} + +func TestFernetCannotDecryptWithoutRightKey(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + + crypter := NewFernet(key) + er, err := crypter.Encrypt([]byte("Hello again world")) + require.NoError(t, err) + + crypter = NewFernet([]byte{}) + _, err = crypter.Decrypt(*er) + require.Error(t, err) +} diff --git a/manager/encryption/nacl_test.go b/manager/encryption/nacl_test.go index da3f30d2ea..329046ed76 100644 --- a/manager/encryption/nacl_test.go +++ b/manager/encryption/nacl_test.go @@ -16,26 +16,31 @@ func TestNACLSecretbox(t *testing.T) { key := make([]byte, 32) _, err := io.ReadFull(cryptorand.Reader, key) require.NoError(t, err) + keyCopy := make([]byte, 32) + copy(key, keyCopy) - crypter := NewNACLSecretbox(key) + crypter1 := NewNACLSecretbox(key) + crypter2 := NewNACLSecretbox(keyCopy) data := []byte("Hello again world") - er1, err := crypter.Encrypt(data) + er1, err := crypter1.Encrypt(data) require.NoError(t, err) - er2, err := crypter.Encrypt(data) + er2, err := crypter1.Encrypt(data) require.NoError(t, err) require.NotEqual(t, er1.Data, er2.Data) - require.NotEmpty(t, er1.Nonce, er2.Nonce) - - result, err := crypter.Decrypt(*er1) - require.NoError(t, err) - require.Equal(t, data, result) - - result, err = crypter.Decrypt(*er2) - require.NoError(t, err) - require.Equal(t, data, result) + require.NotEmpty(t, er1.Nonce) + require.NotEmpty(t, er2.Nonce) + + // both crypters can decrypt the other's text + for _, decrypter := range []Decrypter{crypter1, crypter2} { + for _, record := range []*api.MaybeEncryptedRecord{er1, er2} { + result, err := decrypter.Decrypt(*record) + require.NoError(t, err) + require.Equal(t, data, result) + } + } } func TestNACLSecretboxInvalidAlgorithm(t *testing.T) { From 96129bdbf8a69e4549fe660df5236c67e8dc833d Mon Sep 17 00:00:00 2001 From: Ying Li Date: Mon, 26 Feb 2018 17:19:20 -0800 Subject: [PATCH 03/15] Move MultiDecrypter to encryption package and provide some niceties around constructing one. Also make it a map instead of a list so that as available algorithms increase not every single algorithm needs to be tried to decrypt. Signed-off-by: Ying Li (cherry picked from commit 34ac12e05e5c07fd10731aa04662232a6afe15a8) --- cmd/swarm-rafttool/common.go | 2 +- cmd/swarm-rafttool/dump.go | 2 +- manager/encryption/encryption.go | 54 +++++++++++++++++++++++++++ manager/encryption/encryption_test.go | 39 +++++++++++++++++++ manager/state/raft/storage/storage.go | 17 +-------- 5 files changed, 96 insertions(+), 18 deletions(-) diff --git a/cmd/swarm-rafttool/common.go b/cmd/swarm-rafttool/common.go index 4d14eb2832..532696d47b 100644 --- a/cmd/swarm-rafttool/common.go +++ b/cmd/swarm-rafttool/common.go @@ -78,7 +78,7 @@ func decryptRaftData(swarmdir, outdir, unlockKey string) error { _, d := encryption.Defaults(deks.CurrentDEK) if deks.PendingDEK == nil { _, d2 := encryption.Defaults(deks.PendingDEK) - d = storage.MultiDecrypter{d, d2} + d = encryption.NewMultiDecrypter(d, d2) } snapDir := filepath.Join(outdir, "snap-decrypted") diff --git a/cmd/swarm-rafttool/dump.go b/cmd/swarm-rafttool/dump.go index 97d90d5f1c..e4e1196ecf 100644 --- a/cmd/swarm-rafttool/dump.go +++ b/cmd/swarm-rafttool/dump.go @@ -41,7 +41,7 @@ func loadData(swarmdir, unlockKey string) (*storage.WALData, *raftpb.Snapshot, e _, d := encryption.Defaults(deks.CurrentDEK) if deks.PendingDEK == nil { _, d2 := encryption.Defaults(deks.PendingDEK) - d = storage.MultiDecrypter{d, d2} + d = encryption.NewMultiDecrypter(d, d2) } walFactory = storage.NewWALFactory(encryption.NoopCrypter, d) diff --git a/manager/encryption/encryption.go b/manager/encryption/encryption.go index 7461631b33..b4131df6b1 100644 --- a/manager/encryption/encryption.go +++ b/manager/encryption/encryption.go @@ -59,6 +59,60 @@ func (n noopCrypter) Algorithm() api.MaybeEncryptedRecord_Algorithm { // decrypt any data var NoopCrypter = noopCrypter{} +// specificDecryptor represents a specific type of Decrypter, like NaclSecretbox or Fernet. +// It does not apply to a more general decrypter like MultiDecrypter. +type specificDecrypter interface { + Decrypter + Algorithm() api.MaybeEncryptedRecord_Algorithm +} + +// MultiDecrypter is a decrypter that will attempt to decrypt with multiple decrypters. It +// references them by algorithm, so that only the relevant decrypters are checked instead of +// every single one. The reason for multiple decrypters per algorithm is to support hitless +// encryption key rotation. +// +// For raft encryption for instance, during an encryption key rotation, it's possible to have +// some raft logs encrypted with the old key and some encrypted with the new key, so we need a +// decrypter that can decrypt both. +type MultiDecrypter struct { + decrypters map[api.MaybeEncryptedRecord_Algorithm][]Decrypter +} + +// Decrypt tries to decrypt using any decrypters that match the given algorithm. +func (m MultiDecrypter) Decrypt(r api.MaybeEncryptedRecord) (result []byte, err error) { + decrypters, ok := m.decrypters[r.Algorithm] + if !ok { + return nil, fmt.Errorf("cannot decrypt record encrypted using %s", + api.MaybeEncryptedRecord_Algorithm_name[int32(r.Algorithm)]) + } + for _, d := range decrypters { + result, err = d.Decrypt(r) + if err == nil { + return + } + } + return +} + +// NewMultiDecrypter returns a new MultiDecrypter given multiple Decrypters. If any of +// the Decrypters are also MultiDecrypters, they are flattened into a single map, but +// it does not deduplicate any decrypters. +// Note that if something is neither a MultiDecrypter nor a specificDecrypter, it is +// ignored. +func NewMultiDecrypter(decrypters ...Decrypter) MultiDecrypter { + m := MultiDecrypter{decrypters: make(map[api.MaybeEncryptedRecord_Algorithm][]Decrypter)} + for _, d := range decrypters { + if md, ok := d.(MultiDecrypter); ok { + for algo, dec := range md.decrypters { + m.decrypters[algo] = append(m.decrypters[algo], dec...) + } + } else if sd, ok := d.(specificDecrypter); ok { + m.decrypters[sd.Algorithm()] = append(m.decrypters[sd.Algorithm()], sd) + } + } + return m +} + // Decrypt turns a slice of bytes serialized as an MaybeEncryptedRecord into a slice of plaintext bytes func Decrypt(encryptd []byte, decrypter Decrypter) ([]byte, error) { if decrypter == nil { diff --git a/manager/encryption/encryption_test.go b/manager/encryption/encryption_test.go index 14f2d9a728..94c7b70818 100644 --- a/manager/encryption/encryption_test.go +++ b/manager/encryption/encryption_test.go @@ -1,6 +1,7 @@ package encryption import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -69,3 +70,41 @@ func TestHumanReadable(t *testing.T) { _, err = ParseHumanReadableKey(keyString + "=") require.Error(t, err) } + +type bothCrypter interface { + Decrypter + Encrypter +} + +func TestMultiDecryptor(t *testing.T) { + crypters := []bothCrypter{ + noopCrypter{}, + NewNACLSecretbox([]byte("key1")), + NewNACLSecretbox([]byte("key2")), + NewNACLSecretbox([]byte("key3")), + NewFernet([]byte("key1")), + NewFernet([]byte("key2")), + } + m := NewMultiDecrypter( + crypters[0], crypters[1], crypters[2], crypters[4], + NewMultiDecrypter(crypters[3], crypters[5]), + ) + + for i, c := range crypters { + plaintext := []byte(fmt.Sprintf("message %d", i)) + ciphertext, err := Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err := Decrypt(ciphertext, m) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // for sanity, make sure the other crypters can't decrypt + for j, o := range crypters { + if j == i { + continue + } + _, err := Decrypt(ciphertext, o) + require.IsType(t, ErrCannotDecrypt{}, err) + } + } +} diff --git a/manager/state/raft/storage/storage.go b/manager/state/raft/storage/storage.go index 539e05d5ce..764e5dbc16 100644 --- a/manager/state/raft/storage/storage.go +++ b/manager/state/raft/storage/storage.go @@ -13,7 +13,6 @@ import ( "github.com/coreos/etcd/snap" "github.com/coreos/etcd/wal" "github.com/coreos/etcd/wal/walpb" - "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/encryption" "github.com/pkg/errors" @@ -34,20 +33,6 @@ var versionedWALSnapDirs = []walSnapDirs{ {wal: "wal", snap: "snap"}, } -// MultiDecrypter attempts to decrypt with a list of decrypters -type MultiDecrypter []encryption.Decrypter - -// Decrypt tries to decrypt using all the decrypters -func (m MultiDecrypter) Decrypt(r api.MaybeEncryptedRecord) (result []byte, err error) { - for _, d := range m { - result, err = d.Decrypt(r) - if err == nil { - return - } - } - return -} - // EncryptedRaftLogger saves raft data to disk type EncryptedRaftLogger struct { StateDir string @@ -75,7 +60,7 @@ func (e *EncryptedRaftLogger) BootstrapFromDisk(ctx context.Context, oldEncrypti _, d := encryption.Defaults(key) decrypters = append(decrypters, d) } - decrypter = MultiDecrypter(decrypters) + decrypter = encryption.NewMultiDecrypter(decrypters...) } snapFactory := NewSnapFactory(encrypter, decrypter) From 9bcceab9621da9faf8057662d574e75d13de3b2b Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 27 Feb 2018 16:38:54 -0800 Subject: [PATCH 04/15] Move fips environment variable check to its own package, so we can check it from the encryption package to determine the encryption defaults. Signed-off-by: Ying Li (cherry picked from commit 899202ee078b612476b36e817afc4ffe2a767394) --- ca/certificates.go | 3 +- ca/certificates_test.go | 5 +-- ca/keyutils/keyutils.go | 18 +++------- ca/keyutils/keyutils_test.go | 35 +++++++++---------- fips/fips.go | 11 ++++++ integration/integration_test.go | 14 ++++---- manager/encryption/encryption.go | 7 +++- manager/encryption/encryption_test.go | 49 +++++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 fips/fips.go diff --git a/ca/certificates.go b/ca/certificates.go index 54c3b296f9..a5b31d9cce 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -29,6 +29,7 @@ import ( "github.com/docker/swarmkit/ca/keyutils" "github.com/docker/swarmkit/ca/pkcs8" "github.com/docker/swarmkit/connectionbroker" + "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/ioutils" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -818,7 +819,7 @@ func CreateRootCA(rootCN string) (RootCA, error) { } // Convert key to PKCS#8 in FIPS mode - if keyutils.FIPSEnabled() { + if fips.Enabled() { key, err = pkcs8.ConvertECPrivateKeyPEM(key) if err != nil { return RootCA{}, err diff --git a/ca/certificates_test.go b/ca/certificates_test.go index f43a192331..20269829c9 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -31,6 +31,7 @@ import ( "github.com/docker/swarmkit/ca/pkcs8" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/connectionbroker" + "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager/state" "github.com/docker/swarmkit/manager/state/store" @@ -94,8 +95,8 @@ func TestCreateRootCAKeyFormat(t *testing.T) { require.Equal(t, "EC PRIVATE KEY", block.Type) // Check if the CA key generated is PKCS#8 when FIPS-mode is on - os.Setenv(keyutils.FIPSEnvVar, "1") - defer os.Unsetenv(keyutils.FIPSEnvVar) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) rootCA, err = ca.CreateRootCA("rootCA") require.NoError(t, err) diff --git a/ca/keyutils/keyutils.go b/ca/keyutils/keyutils.go index ee6ad8fc16..03874428c0 100644 --- a/ca/keyutils/keyutils.go +++ b/ca/keyutils/keyutils.go @@ -10,22 +10,14 @@ import ( "crypto/x509" "encoding/pem" "errors" - "os" "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/ca/pkcs8" + "github.com/docker/swarmkit/fips" ) var errFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance") -// FIPSEnvVar is the environment variable which stores FIPS mode state -const FIPSEnvVar = "GOFIPS" - -// FIPSEnabled returns true when FIPS mode is enabled -func FIPSEnabled() bool { - return os.Getenv(FIPSEnvVar) != "" -} - // IsPKCS8 returns true if the provided der bytes is encrypted/unencrypted PKCS#8 key func IsPKCS8(derBytes []byte) bool { if _, err := x509.ParsePKCS8PrivateKey(derBytes); err == nil { @@ -49,7 +41,7 @@ func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, e if IsPKCS8(block.Bytes) { return pkcs8.ParsePrivateKeyPEMWithPassword(pemBytes, password) - } else if FIPSEnabled() { + } else if fips.Enabled() { return nil, errFIPSUnsupportedKeyFormat } @@ -59,7 +51,7 @@ func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, e // IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not // It returns false in FIPS mode even if PKCS#1 is encrypted func IsEncryptedPEMBlock(block *pem.Block) bool { - return pkcs8.IsEncryptedPEMBlock(block) || (!FIPSEnabled() && x509.IsEncryptedPEMBlock(block)) + return pkcs8.IsEncryptedPEMBlock(block) || (!fips.Enabled() && x509.IsEncryptedPEMBlock(block)) } // DecryptPEMBlock requires PKCS#1 or PKCS#8 PEM Block and password to decrypt and return unencrypted der []byte @@ -67,7 +59,7 @@ func IsEncryptedPEMBlock(block *pem.Block) bool { func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { if IsPKCS8(block.Bytes) { return pkcs8.DecryptPEMBlock(block, password) - } else if FIPSEnabled() { + } else if fips.Enabled() { return nil, errFIPSUnsupportedKeyFormat } @@ -79,7 +71,7 @@ func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { func EncryptPEMBlock(data, password []byte) (*pem.Block, error) { if IsPKCS8(data) { return pkcs8.EncryptPEMBlock(data, password) - } else if FIPSEnabled() { + } else if fips.Enabled() { return nil, errFIPSUnsupportedKeyFormat } diff --git a/ca/keyutils/keyutils_test.go b/ca/keyutils/keyutils_test.go index 15f52d69ef..1e01551a3e 100644 --- a/ca/keyutils/keyutils_test.go +++ b/ca/keyutils/keyutils_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/docker/swarmkit/fips" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,12 +51,12 @@ aMbljbOLAjpZS3/VnQteab4= ) func TestFIPSEnabled(t *testing.T) { - os.Unsetenv(FIPSEnvVar) - assert.False(t, FIPSEnabled()) + os.Unsetenv(fips.EnvVar) + assert.False(t, fips.Enabled()) - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - assert.True(t, FIPSEnabled()) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) + assert.True(t, fips.Enabled()) } func TestIsPKCS8(t *testing.T) { @@ -70,7 +71,7 @@ func TestIsPKCS8(t *testing.T) { func TestIsEncryptedPEMBlock(t *testing.T) { // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) + os.Unsetenv(fips.EnvVar) // Check PKCS8 keys assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) @@ -81,8 +82,8 @@ func TestIsEncryptedPEMBlock(t *testing.T) { assert.True(t, IsEncryptedPEMBlock(encryptedPKCS1Block)) // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) // Check PKCS8 keys again assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) @@ -95,7 +96,7 @@ func TestIsEncryptedPEMBlock(t *testing.T) { func TestDecryptPEMBlock(t *testing.T) { // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) + os.Unsetenv(fips.EnvVar) // Check PKCS8 keys _, err := DecryptPEMBlock(encryptedPKCS8Block, []byte("pony")) @@ -114,8 +115,8 @@ func TestDecryptPEMBlock(t *testing.T) { require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) // Try to decrypt PKCS1 _, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) @@ -124,7 +125,7 @@ func TestDecryptPEMBlock(t *testing.T) { func TestEncryptPEMBlock(t *testing.T) { // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) + os.Unsetenv(fips.EnvVar) // Check PKCS8 keys encryptedBlock, err := EncryptPEMBlock(decryptedPKCS8Block.Bytes, []byte("knock knock")) @@ -151,8 +152,8 @@ func TestEncryptPEMBlock(t *testing.T) { require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) // Try to encrypt PKCS1 _, err = EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) @@ -161,7 +162,7 @@ func TestEncryptPEMBlock(t *testing.T) { func TestParsePrivateKeyPEMWithPassword(t *testing.T) { // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) + os.Unsetenv(fips.EnvVar) // Check PKCS8 keys _, err := ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("pony")) @@ -184,8 +185,8 @@ func TestParsePrivateKeyPEMWithPassword(t *testing.T) { require.NoError(t, err) // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) + os.Setenv(fips.EnvVar, "1") + defer os.Unsetenv(fips.EnvVar) // Try to parse PKCS1 _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) diff --git a/fips/fips.go b/fips/fips.go new file mode 100644 index 0000000000..9fde7772ee --- /dev/null +++ b/fips/fips.go @@ -0,0 +1,11 @@ +package fips + +import "os" + +// EnvVar is the environment variable which stores FIPS mode state +const EnvVar = "GOFIPS" + +// Enabled returns true when FIPS mode is enabled +func Enabled() bool { + return os.Getenv(EnvVar) != "" +} diff --git a/integration/integration_test.go b/integration/integration_test.go index b559df986e..8f1f2c29d5 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -20,8 +20,8 @@ import ( events "github.com/docker/go-events" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" cautils "github.com/docker/swarmkit/ca/testutils" + "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager" "github.com/docker/swarmkit/testutils" @@ -269,12 +269,12 @@ func TestAutolockManagers(t *testing.T) { t.Parallel() // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(keyutils.FIPSEnvVar) + defer os.Unsetenv(fips.EnvVar) for _, pkcs1 := range []bool{true, false} { if pkcs1 { - os.Unsetenv(keyutils.FIPSEnvVar) + os.Unsetenv(fips.EnvVar) } else { - os.Setenv(keyutils.FIPSEnvVar, "1") + os.Setenv(fips.EnvVar, "1") } rootCA, err := ca.CreateRootCA("rootCN") @@ -622,12 +622,12 @@ func TestSuccessfulRootRotation(t *testing.T) { t.Parallel() // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(keyutils.FIPSEnvVar) + defer os.Unsetenv(fips.EnvVar) for _, pkcs1 := range []bool{true, false} { if pkcs1 { - os.Unsetenv(keyutils.FIPSEnvVar) + os.Unsetenv(fips.EnvVar) } else { - os.Setenv(keyutils.FIPSEnvVar, "1") + os.Setenv(fips.EnvVar, "1") } rootCA, err := ca.CreateRootCA("rootCN") diff --git a/manager/encryption/encryption.go b/manager/encryption/encryption.go index b4131df6b1..5b20f1ec8d 100644 --- a/manager/encryption/encryption.go +++ b/manager/encryption/encryption.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/fips" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" ) @@ -151,8 +152,12 @@ func Encrypt(plaintext []byte, encrypter Encrypter) ([]byte, error) { // Defaults returns a default encrypter and decrypter func Defaults(key []byte) (Encrypter, Decrypter) { + f := NewFernet(key) + if fips.Enabled() { + return f, f + } n := NewNACLSecretbox(key) - return n, n + return n, NewMultiDecrypter(n, f) } // GenerateSecretKey generates a secret key that can be used for encrypting data diff --git a/manager/encryption/encryption_test.go b/manager/encryption/encryption_test.go index 94c7b70818..c974445e3a 100644 --- a/manager/encryption/encryption_test.go +++ b/manager/encryption/encryption_test.go @@ -2,8 +2,11 @@ package encryption import ( "fmt" + "os" "testing" + "github.com/docker/swarmkit/fips" + "github.com/stretchr/testify/require" ) @@ -108,3 +111,49 @@ func TestMultiDecryptor(t *testing.T) { } } } + +// The default encrypter/decrypter, if FIPS is not enabled, is NACLSecretBox. +// However, it can decrypt using all other supported algorithms. If FIPS is +// enabled, the encrypter/decrypter is Fernet only, because FIPS only permits +// (given the algorithms swarmkit supports) AES-128-CBC +func TestDefaults(t *testing.T) { + oldFipsVar := os.Getenv(fips.EnvVar) + + plaintext := []byte("my message") + + // ensure the fips var is not set + require.NoError(t, os.Unsetenv(fips.EnvVar)) + c, d := Defaults([]byte("key")) + ciphertext, err := Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err := Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // ensure that the fips var is set - defaults should return a fernet encrypter + // and a decrypter that can't decrypt nacl + require.NoError(t, os.Setenv(fips.EnvVar, "true")) + c, d = Defaults([]byte("key")) + _, err = Decrypt(ciphertext, d) + require.Error(t, err) + ciphertext, err = Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err = Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // unset the fips var again, and ensure we can decrypt the previous ciphertext + // (encrypted with fernet) with the decrypter returned by defaults + require.NoError(t, os.Unsetenv(fips.EnvVar)) + _, d = Defaults([]byte("key")) + decrypted, err = Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // put the env var back + if oldFipsVar == "" { + require.NoError(t, os.Unsetenv(fips.EnvVar)) + } else { + require.NoError(t, os.Setenv(fips.EnvVar, oldFipsVar)) + } +} From 3aebbb2573c2506e4f8c5f121fc54f7641cd4ffa Mon Sep 17 00:00:00 2001 From: Ying Li Date: Mon, 26 Mar 2018 17:38:10 -0700 Subject: [PATCH 05/15] Stop encrypting the raft root CA key entirely based on env vars, since that feature was deprecated almost a year ago. Rely on MTLS for encryption in transit and raft log encryption for encryption at rest. Signed-off-by: Ying Li (cherry picked from commit 9369c1eff40e7fb6f821194ce1a6973a3ec7605f) --- ca/certificates.go | 66 +---------------- ca/certificates_test.go | 79 -------------------- manager/manager.go | 116 ++---------------------------- manager/manager_test.go | 155 ---------------------------------------- 4 files changed, 7 insertions(+), 409 deletions(-) diff --git a/ca/certificates.go b/ca/certificates.go index a5b31d9cce..6bad7f9a9c 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -52,13 +52,6 @@ const ( RootKeySize = 256 // RootKeyAlgo defines the default algorithm for the root CA Key RootKeyAlgo = "ecdsa" - // PassphraseENVVar defines the environment variable to look for the - // root CA private key material encryption key - PassphraseENVVar = "SWARM_ROOT_CA_PASSPHRASE" - // PassphraseENVVarPrev defines the alternate environment variable to look for the - // root CA private key material encryption key. It can be used for seamless - // KEK rotations. - PassphraseENVVarPrev = "SWARM_ROOT_CA_PASSPHRASE_PREV" // RootCAExpiration represents the default expiration for the root CA in seconds (20 years) RootCAExpiration = "630720000s" // DefaultNodeCertExpiration represents the default expiration for node certificates (3 months) @@ -642,28 +635,10 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo return nil, errors.Wrap(err, "error while validating signing CA certificate against roots and intermediates") } - var ( - passphraseStr string - passphrase, passphrasePrev []byte - priv crypto.Signer - ) - - // Attempt two distinct passphrases, so we can do a hitless passphrase rotation - if passphraseStr = os.Getenv(PassphraseENVVar); passphraseStr != "" { - passphrase = []byte(passphraseStr) - } - - if p := os.Getenv(PassphraseENVVarPrev); p != "" { - passphrasePrev = []byte(p) - } - - // Attempt to decrypt the current private-key with the passphrases provided - priv, err = keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, passphrase) + // The key should not be encrypted, but it could be in PKCS8 format rather than PKCS1 + priv, err := keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, nil) if err != nil { - priv, err = keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, passphrasePrev) - if err != nil { - return nil, errors.Wrap(err, "malformed private key") - } + return nil, errors.Wrap(err, "malformed private key") } // We will always use the first certificate inside of the root bundle as the active one @@ -676,17 +651,6 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo return nil, err } - // If the key was loaded from disk unencrypted, but there is a passphrase set, - // ensure it is encrypted, so it doesn't hit raft in plain-text - // we don't have to check for nil, because if we couldn't pem-decode the bytes, then parsing above would have failed - keyBlock, _ := pem.Decode(keyBytes) - if passphraseStr != "" && !keyutils.IsEncryptedPEMBlock(keyBlock) { - keyBytes, err = EncryptECPrivateKey(keyBytes, passphraseStr) - if err != nil { - return nil, errors.Wrap(err, "unable to encrypt signing CA key material") - } - } - return &LocalSigner{Cert: certBytes, Key: keyBytes, Signer: signer, parsedCert: parsedCerts[0], cryptoSigner: priv}, nil } @@ -977,30 +941,6 @@ func GenerateNewCSR() ([]byte, []byte, error) { return csr, key, err } -// EncryptECPrivateKey receives a PEM encoded private key and returns an encrypted -// AES256 version using a passphrase -// TODO: Make this method generic to handle RSA keys -func EncryptECPrivateKey(key []byte, passphraseStr string) ([]byte, error) { - passphrase := []byte(passphraseStr) - - keyBlock, _ := pem.Decode(key) - if keyBlock == nil { - // This RootCA does not have a valid signer. - return nil, errors.New("error while decoding PEM key") - } - - encryptedPEMBlock, err := keyutils.EncryptPEMBlock(keyBlock.Bytes, passphrase) - if err != nil { - return nil, err - } - - if encryptedPEMBlock.Headers == nil { - return nil, errors.New("unable to encrypt key - invalid PEM file produced") - } - - return pem.EncodeToMemory(encryptedPEMBlock), nil -} - // NormalizePEMs takes a bundle of PEM-encoded certificates in a certificate bundle, // decodes them, removes headers, and re-encodes them to make sure that they have // consistent whitespace. Note that this is intended to normalize x509 certificates diff --git a/ca/certificates_test.go b/ca/certificates_test.go index 20269829c9..205534a6ef 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -27,8 +27,6 @@ import ( "github.com/docker/go-events" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" - "github.com/docker/swarmkit/ca/pkcs8" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/connectionbroker" "github.com/docker/swarmkit/fips" @@ -45,9 +43,6 @@ import ( ) func init() { - os.Setenv(ca.PassphraseENVVar, "") - os.Setenv(ca.PassphraseENVVarPrev, "") - ca.RenewTLSExponentialBackoff = events.ExponentialBackoffConfig{ Base: 250 * time.Millisecond, Factor: 250 * time.Millisecond, @@ -232,21 +227,6 @@ some random garbage\n require.Error(t, err) } -func TestEncryptECPrivateKey(t *testing.T) { - tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") - assert.NoError(t, err) - defer os.RemoveAll(tempBaseDir) - - _, key, err := ca.GenerateNewCSR() - assert.NoError(t, err) - encryptedKey, err := ca.EncryptECPrivateKey(key, "passphrase") - assert.NoError(t, err) - - keyBlock, _ := pem.Decode(encryptedKey) - assert.NotNil(t, keyBlock) - assert.True(t, pkcs8.IsEncryptedPEMBlock(keyBlock)) -} - func TestParseValidateAndSignCSR(t *testing.T) { rootCA, err := ca.CreateRootCA("rootCN") assert.NoError(t, err) @@ -1332,65 +1312,6 @@ func TestRootCAWithCrossSignedIntermediates(t *testing.T) { checkValidateAgainstAllRoots(tlsCert) } -func TestNewRootCAWithPassphrase(t *testing.T) { - defer os.Setenv(ca.PassphraseENVVar, "") - defer os.Setenv(ca.PassphraseENVVarPrev, "") - - rootCA, err := ca.CreateRootCA("rootCN") - assert.NoError(t, err) - rcaSigner, err := rootCA.Signer() - assert.NoError(t, err) - - // Ensure that we're encrypting the Key bytes out of NewRoot if there - // is a passphrase set as an env Var - os.Setenv(ca.PassphraseENVVar, "password1") - newRootCA, err := ca.NewRootCA(rootCA.Certs, rcaSigner.Cert, rcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - nrcaSigner, err := newRootCA.Signer() - assert.NoError(t, err) - assert.NotEqual(t, rcaSigner.Key, nrcaSigner.Key) - assert.Equal(t, rootCA.Certs, newRootCA.Certs) - assert.NotContains(t, string(rcaSigner.Key), string(nrcaSigner.Key)) - keyBlock, _ := pem.Decode(nrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) - - // Ensure that we're decrypting the Key bytes out of NewRoot if there - // is a passphrase set as an env Var - anotherNewRootCA, err := ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - anrcaSigner, err := anotherNewRootCA.Signer() - assert.NoError(t, err) - assert.Equal(t, newRootCA, anotherNewRootCA) - assert.NotContains(t, string(rcaSigner.Key), string(anrcaSigner.Key)) - keyBlock, _ = pem.Decode(anrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) - - // Ensure that we cant decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var - os.Setenv(ca.PassphraseENVVar, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.Error(t, err) - - // Ensure that we cant decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var - os.Setenv(ca.PassphraseENVVarPrev, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.Error(t, err) - - // Ensure that we can decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var, but a valid as Prev - os.Setenv(ca.PassphraseENVVarPrev, "password1") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - assert.Equal(t, newRootCA, anotherNewRootCA) - assert.NotContains(t, string(rcaSigner.Key), string(anrcaSigner.Key)) - keyBlock, _ = pem.Decode(anrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) -} - type certTestCase struct { cert []byte errorStr string diff --git a/manager/manager.go b/manager/manager.go index 84716b3217..879dad035e 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -2,7 +2,6 @@ package manager import ( "crypto/tls" - "encoding/pem" "fmt" "net" "os" @@ -17,7 +16,6 @@ import ( gmetrics "github.com/docker/go-metrics" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" "github.com/docker/swarmkit/connectionbroker" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/log" @@ -768,109 +766,6 @@ func (m *Manager) watchForClusterChanges(ctx context.Context) error { return nil } -// rotateRootCAKEK will attempt to rotate the key-encryption-key for root CA key-material in raft. -// If there is no passphrase set in ENV, it returns. -// If there is plain-text root key-material, and a passphrase set, it encrypts it. -// If there is encrypted root key-material and it is using the current passphrase, it returns. -// If there is encrypted root key-material, and it is using the previous passphrase, it -// re-encrypts it with the current passphrase. -func (m *Manager) rotateRootCAKEK(ctx context.Context, clusterID string) error { - // If we don't have a KEK, we won't ever be rotating anything - strPassphrase := os.Getenv(ca.PassphraseENVVar) - strPassphrasePrev := os.Getenv(ca.PassphraseENVVarPrev) - if strPassphrase == "" && strPassphrasePrev == "" { - return nil - } - if strPassphrase != "" { - log.G(ctx).Warn("Encrypting the root CA key in swarm using environment variables is deprecated. " + - "Support for decrypting or rotating the key will be removed in the future.") - } - - passphrase := []byte(strPassphrase) - passphrasePrev := []byte(strPassphrasePrev) - - s := m.raftNode.MemoryStore() - var ( - cluster *api.Cluster - err error - finalKey []byte - ) - // Retrieve the cluster identified by ClusterID - return s.Update(func(tx store.Tx) error { - cluster = store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster not found: %s", clusterID) - } - - // Try to get the private key from the cluster - privKeyPEM := cluster.RootCA.CAKey - if len(privKeyPEM) == 0 { - // We have no PEM root private key in this cluster. - log.G(ctx).Warnf("cluster %s does not have private key material", clusterID) - return nil - } - - // Decode the PEM private key - keyBlock, _ := pem.Decode(privKeyPEM) - if keyBlock == nil { - return fmt.Errorf("invalid PEM-encoded private key inside of cluster %s", clusterID) - } - - if keyutils.IsEncryptedPEMBlock(keyBlock) { - // PEM encryption does not have a digest, so sometimes decryption doesn't - // error even with the wrong passphrase. So actually try to parse it into a valid key. - _, err := keyutils.ParsePrivateKeyPEMWithPassword(privKeyPEM, []byte(passphrase)) - if err == nil { - // This key is already correctly encrypted with the correct KEK, nothing to do here - return nil - } - - // This key is already encrypted, but failed with current main passphrase. - // Let's try to decrypt with the previous passphrase, and parse into a valid key, for the - // same reason as above. - _, err = keyutils.ParsePrivateKeyPEMWithPassword(privKeyPEM, []byte(passphrasePrev)) - if err != nil { - // We were not able to decrypt either with the main or backup passphrase, error - return err - } - // ok the above passphrase is correct, so decrypt the PEM block so we can re-encrypt - - // since the key was successfully decrypted above, there will be no error doing PEM - // decryption - unencryptedDER, _ := keyutils.DecryptPEMBlock(keyBlock, []byte(passphrasePrev)) - unencryptedKeyBlock := &pem.Block{ - Type: keyBlock.Type, - Bytes: unencryptedDER, - } - - // we were able to decrypt the key with the previous passphrase - if the current passphrase is empty, - // the we store the decrypted key in raft - finalKey = pem.EncodeToMemory(unencryptedKeyBlock) - - // the current passphrase is not empty, so let's encrypt with the new one and store it in raft - if strPassphrase != "" { - finalKey, err = ca.EncryptECPrivateKey(finalKey, strPassphrase) - if err != nil { - log.G(ctx).WithError(err).Debugf("failed to rotate the key-encrypting-key for the root key material of cluster %s", clusterID) - return err - } - } - } else if strPassphrase != "" { - // If this key is not encrypted, and the passphrase is not nil, then we have to encrypt it - finalKey, err = ca.EncryptECPrivateKey(privKeyPEM, strPassphrase) - if err != nil { - log.G(ctx).WithError(err).Debugf("failed to rotate the key-encrypting-key for the root key material of cluster %s", clusterID) - return err - } - } else { - return nil // don't update if it's not encrypted and we don't want it encrypted - } - - log.G(ctx).Infof("Updating the encryption on the root key material of cluster %s", clusterID) - cluster.RootCA.CAKey = finalKey - return store.UpdateCluster(tx, cluster) - }) -} - // handleLeadershipEvents handles the is leader event or is follower event. func (m *Manager) handleLeadershipEvents(ctx context.Context, leadershipCh chan events.Event) { for { @@ -938,7 +833,10 @@ func (m *Manager) becomeLeader(ctx context.Context) { initialCAConfig := ca.DefaultCAConfig() initialCAConfig.ExternalCAs = m.config.ExternalCAs - var unlockKeys []*api.EncryptionKey + var ( + unlockKeys []*api.EncryptionKey + err error + ) if m.config.AutoLockManagers { unlockKeys = []*api.EncryptionKey{{ Subsystem: ca.ManagerRole, @@ -991,12 +889,6 @@ func (m *Manager) becomeLeader(ctx context.Context) { return nil }) - // Attempt to rotate the key-encrypting-key of the root CA key-material - err := m.rotateRootCAKEK(ctx, clusterID) - if err != nil { - log.G(ctx).WithError(err).Error("root key-encrypting-key rotation failed") - } - m.replicatedOrchestrator = replicated.NewReplicatedOrchestrator(s) m.constraintEnforcer = constraintenforcer.New(s) m.globalOrchestrator = global.NewGlobalOrchestrator(s) diff --git a/manager/manager_test.go b/manager/manager_test.go index e22fcafd4c..e718c95f1c 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -435,158 +435,3 @@ func TestManagerLockUnlock(t *testing.T) { // error. <-done } - -// Tests manager rotates encryption of root key data in the raft store -func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { - tc := cautils.NewTestCA(t) - defer tc.Stop() - - temp, err := ioutil.TempFile("", "test-socket") - require.NoError(t, err) - require.NoError(t, temp.Close()) - require.NoError(t, os.Remove(temp.Name())) - - defer os.RemoveAll(temp.Name()) - - stateDir, err := ioutil.TempDir("", "test-raft") - require.NoError(t, err) - defer os.RemoveAll(stateDir) - - managerSecurityConfig, err := tc.NewNodeConfig(ca.ManagerRole) - require.NoError(t, err) - - _, _, err = managerSecurityConfig.KeyReader().Read() - require.NoError(t, err) - - config := Config{ - RemoteAPI: &RemoteAddrs{ListenAddr: "127.0.0.1:0"}, - ControlAPI: temp.Name(), - StateDir: stateDir, - SecurityConfig: managerSecurityConfig, - RootCAPaths: tc.Paths.RootCA, - } - done := make(chan error) - defer close(done) - - var m *Manager - startManager := func() { - m, err = New(&config) - require.NoError(t, err) - require.NotNil(t, m) - - go func() { - done <- m.Run(tc.Context) - }() - } - - startManager() - - var clusterID string - // wait for cluster data to be there - err = testutils.PollFunc(nil, func() error { - // using store.Update just because it returns an error, as opposed to store.View - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - clusters, err := store.FindClusters(tx, store.All) - if err != nil { - return err - } - if len(clusters) != 1 { - return fmt.Errorf("expected 1 cluster, got %d", len(clusters)) - } - clusterID = clusters[0].ID - return nil - }) - }) - - os.Setenv(ca.PassphraseENVVar, "kek") - defer os.Unsetenv(ca.PassphraseENVVar) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - - // wait for the key to be encrypted in the raft store - err = testutils.PollFunc(nil, func() error { - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - keyBlock, _ := pem.Decode(cluster.RootCA.CAKey) - if keyBlock == nil { - return fmt.Errorf("could not pem decode root key") - } - if !keyutils.IsEncryptedPEMBlock(keyBlock) { - return fmt.Errorf("root key material not encrypted yet") - } - _, err = keyutils.DecryptPEMBlock(keyBlock, []byte("kek")) - return err - }) - }) - require.NoError(t, err) - - os.Unsetenv(ca.PassphraseENVVar) - os.Setenv(ca.PassphraseENVVarPrev, "kek") - defer os.Unsetenv(ca.PassphraseENVVarPrev) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - - // wait for the key to be decrypted in the raft store - pollDecrypted := func() error { - return testutils.PollFunc(nil, func() error { - // wait until we are leader first, because otherwise the raft node could still be catching - // up on all the logs on disk and hence not have processed the "encrypt CA key" log yet - if !m.raftNode.IsLeader() { - return fmt.Errorf("node is not leader yet") - } - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - keyBlock, _ := pem.Decode(cluster.RootCA.CAKey) - if keyBlock == nil { - return fmt.Errorf("could not pem decode root key") - } - if keyutils.IsEncryptedPEMBlock(keyBlock) { - return fmt.Errorf("root key material not decrypted yet") - } - return nil - }) - }) - } - require.NoError(t, pollDecrypted()) - - // update the key to that can be "decrypted" with both "" and "kek" as the password. This - // doesn't actually match the root CA certificate, and hence the security config can't be - // updated, but we're just checking that the CA key is decrypted. - require.NoError(t, m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - cluster.RootCA.CAKey = []byte(` ------BEGIN ENCRYPTED PRIVATE KEY----- -MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAiLGJtiTmJ3rQICCAAw -HQYJYIZIAWUDBAEqBBBeDoliB0Qe73DdcMeFCuRzBIGQP/iFMPj9BJ/81GV//fMp -KPozbY0EWodXt7KArbeROd5+uWw1muLANUa3KkkXyQhmzlR2Zv3Y/kBuPay9RweU -md94ZD/HY9K+ISv4tIA7u8gp2Hqr0elfG0QqBuwrh688ZF5jii6umZzXtLVVMvWd -NF7w1CA6b8w1aTIklVjv0AJ9tgtGQb9phVigPAdyyw6v ------END ENCRYPTED PRIVATE KEY----- -`) - return store.UpdateCluster(tx, cluster) - })) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - require.NoError(t, pollDecrypted()) - - m.Stop(tc.Context, false) - <-done -} From 110e8dbb9a9bc07e1a93d37d3bd37d36f15ac249 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Wed, 14 Mar 2018 17:07:42 -0700 Subject: [PATCH 06/15] Rather than use an environment variable to determine whether something requires FIPS: (1) require that users of the keyutil package instead use a key formatter object, which could either be the default non-FIPS utility or the FIPS utility. (2) require that users that request encryption defaults specify whether FIPS compliance is needed Signed-off-by: Ying Li (cherry picked from commit 2a318675f83c5d524c34dbb99c1970fd53934035) --- ca/keyutils/keyutils.go | 51 +++++--- ca/keyutils/keyutils_test.go | 143 ++++++++------------- cmd/swarm-rafttool/common.go | 6 +- cmd/swarm-rafttool/common_test.go | 2 +- cmd/swarm-rafttool/dump.go | 6 +- fips/fips.go | 11 -- manager/deks.go | 4 +- manager/encryption/encryption.go | 8 +- manager/encryption/encryption_test.go | 29 +---- manager/manager_test.go | 2 +- manager/state/raft/storage/storage.go | 11 +- manager/state/raft/storage/storage_test.go | 2 +- 12 files changed, 116 insertions(+), 159 deletions(-) delete mode 100644 fips/fips.go diff --git a/ca/keyutils/keyutils.go b/ca/keyutils/keyutils.go index 03874428c0..ea45aab7dd 100644 --- a/ca/keyutils/keyutils.go +++ b/ca/keyutils/keyutils.go @@ -13,10 +13,28 @@ import ( "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/ca/pkcs8" - "github.com/docker/swarmkit/fips" ) -var errFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance") +// Formatter provides an interface for converting keys to the right format, and encrypting and decrypting keys +type Formatter interface { + ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) + DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) + EncryptPEMBlock(data, password []byte) (*pem.Block, error) +} + +// ErrFIPSUnsupportedKeyFormat is returned when encryption/decryption operations are attempted on a PKCS1 key +// when FIPS mode is enabled. +var ErrFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance") + +// Default is the default key util, where FIPS is not required +var Default Formatter = &utils{fips: false} + +// FIPS is the key utility which enforces FIPS compliance +var FIPS Formatter = &utils{fips: true} + +type utils struct { + fips bool +} // IsPKCS8 returns true if the provided der bytes is encrypted/unencrypted PKCS#8 key func IsPKCS8(derBytes []byte) bool { @@ -31,9 +49,14 @@ func IsPKCS8(derBytes []byte) bool { }) } +// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not +func IsEncryptedPEMBlock(block *pem.Block) bool { + return pkcs8.IsEncryptedPEMBlock(block) || x509.IsEncryptedPEMBlock(block) +} + // ParsePrivateKeyPEMWithPassword parses an encrypted or a decrypted PKCS#1 or PKCS#8 PEM to crypto.Signer. // It returns an error in FIPS mode if PKCS#1 PEM bytes are passed. -func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) { +func (u *utils) ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("Could not parse PEM") @@ -41,26 +64,20 @@ func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, e if IsPKCS8(block.Bytes) { return pkcs8.ParsePrivateKeyPEMWithPassword(pemBytes, password) - } else if fips.Enabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } return helpers.ParsePrivateKeyPEMWithPassword(pemBytes, password) } -// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not -// It returns false in FIPS mode even if PKCS#1 is encrypted -func IsEncryptedPEMBlock(block *pem.Block) bool { - return pkcs8.IsEncryptedPEMBlock(block) || (!fips.Enabled() && x509.IsEncryptedPEMBlock(block)) -} - // DecryptPEMBlock requires PKCS#1 or PKCS#8 PEM Block and password to decrypt and return unencrypted der []byte // It returns an error in FIPS mode when PKCS#1 PEM Block is passed. -func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { +func (u *utils) DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { if IsPKCS8(block.Bytes) { return pkcs8.DecryptPEMBlock(block, password) - } else if fips.Enabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } return x509.DecryptPEMBlock(block, password) @@ -68,11 +85,11 @@ func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { // EncryptPEMBlock takes DER-format bytes and password to return an encrypted PKCS#1 or PKCS#8 PEM-block // It returns an error in FIPS mode when PKCS#1 PEM bytes are passed. -func EncryptPEMBlock(data, password []byte) (*pem.Block, error) { +func (u *utils) EncryptPEMBlock(data, password []byte) (*pem.Block, error) { if IsPKCS8(data) { return pkcs8.EncryptPEMBlock(data, password) - } else if fips.Enabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } cipherType := x509.PEMCipherAES256 diff --git a/ca/keyutils/keyutils_test.go b/ca/keyutils/keyutils_test.go index 1e01551a3e..d0b0d455a7 100644 --- a/ca/keyutils/keyutils_test.go +++ b/ca/keyutils/keyutils_test.go @@ -2,10 +2,8 @@ package keyutils import ( "encoding/pem" - "os" "testing" - "github.com/docker/swarmkit/fips" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,15 +48,6 @@ aMbljbOLAjpZS3/VnQteab4= encryptedPKCS1Block, _ = pem.Decode([]byte(encryptedPKCS1)) ) -func TestFIPSEnabled(t *testing.T) { - os.Unsetenv(fips.EnvVar) - assert.False(t, fips.Enabled()) - - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - assert.True(t, fips.Enabled()) -} - func TestIsPKCS8(t *testing.T) { // Check PKCS8 keys assert.True(t, IsPKCS8([]byte(decryptedPKCS8Block.Bytes))) @@ -70,125 +59,95 @@ func TestIsPKCS8(t *testing.T) { } func TestIsEncryptedPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(fips.EnvVar) - - // Check PKCS8 keys + // Check PKCS8 assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) assert.True(t, IsEncryptedPEMBlock(encryptedPKCS8Block)) - // Check PKCS1 keys + // Check PKCS1 assert.False(t, IsEncryptedPEMBlock(decryptedPKCS1Block)) assert.True(t, IsEncryptedPEMBlock(encryptedPKCS1Block)) - - // Enable FIPS mode - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - - // Check PKCS8 keys again - assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) - assert.True(t, IsEncryptedPEMBlock(encryptedPKCS8Block)) - - // Check PKCS1 keys again - assert.False(t, IsEncryptedPEMBlock(decryptedPKCS1Block)) - assert.False(t, IsEncryptedPEMBlock(encryptedPKCS1Block)) } func TestDecryptPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(fips.EnvVar) - - // Check PKCS8 keys - _, err := DecryptPEMBlock(encryptedPKCS8Block, []byte("pony")) - require.Error(t, err) - - decryptedDer, err := DecryptPEMBlock(encryptedPKCS8Block, []byte("ponies")) - require.NoError(t, err) - require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) - - // Check PKCS1 keys - _, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("pony")) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + _, err := util.DecryptPEMBlock(encryptedPKCS8Block, []byte("pony")) + require.Error(t, err) + + decryptedDer, err := util.DecryptPEMBlock(encryptedPKCS8Block, []byte("ponies")) + require.NoError(t, err) + require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) + } + + // Check PKCS1 keys in non-FIPS mode + _, err := Default.DecryptPEMBlock(encryptedPKCS1Block, []byte("pony")) require.Error(t, err) - decryptedDer, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) + decryptedDer, err := Default.DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) require.NoError(t, err) require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) - // Enable FIPS mode - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - - // Try to decrypt PKCS1 - _, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) + // Try to decrypt PKCS1 in FIPS + _, err = FIPS.DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) require.Error(t, err) } func TestEncryptPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(fips.EnvVar) - - // Check PKCS8 keys - encryptedBlock, err := EncryptPEMBlock(decryptedPKCS8Block.Bytes, []byte("knock knock")) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + encryptedBlock, err := util.EncryptPEMBlock(decryptedPKCS8Block.Bytes, []byte("knock knock")) + require.NoError(t, err) + + // Try to decrypt the same encrypted block + _, err = util.DecryptPEMBlock(encryptedBlock, []byte("hey there")) + require.Error(t, err) + + decryptedDer, err := Default.DecryptPEMBlock(encryptedBlock, []byte("knock knock")) + require.NoError(t, err) + require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) + } + + // Check PKCS1 keys in non FIPS mode + encryptedBlock, err := Default.EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) require.NoError(t, err) // Try to decrypt the same encrypted block - _, err = DecryptPEMBlock(encryptedBlock, []byte("hey there")) + _, err = Default.DecryptPEMBlock(encryptedBlock, []byte("hey there")) require.Error(t, err) - decryptedDer, err := DecryptPEMBlock(encryptedBlock, []byte("knock knock")) - require.NoError(t, err) - require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) - - // Check PKCS1 keys - encryptedBlock, err = EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) - require.NoError(t, err) - - // Try to decrypt the same encrypted block - _, err = DecryptPEMBlock(encryptedBlock, []byte("hey there")) - require.Error(t, err) - - decryptedDer, err = DecryptPEMBlock(encryptedBlock, []byte("knock knock")) + decryptedDer, err := Default.DecryptPEMBlock(encryptedBlock, []byte("knock knock")) require.NoError(t, err) require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) - // Enable FIPS mode - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - // Try to encrypt PKCS1 - _, err = EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) + _, err = FIPS.EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) require.Error(t, err) } func TestParsePrivateKeyPEMWithPassword(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(fips.EnvVar) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + _, err := util.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("pony")) + require.Error(t, err) - // Check PKCS8 keys - _, err := ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("pony")) - require.Error(t, err) + _, err = util.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("ponies")) + require.NoError(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("ponies")) - require.NoError(t, err) + _, err = util.ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS8), nil) + require.NoError(t, err) + } - _, err = ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS8), nil) - require.NoError(t, err) - - // Check PKCS1 keys - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("pony")) + // Check PKCS1 keys in non-FIPS mode + _, err := Default.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("pony")) require.Error(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) + _, err = Default.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) require.NoError(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS1), nil) + _, err = Default.ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS1), nil) require.NoError(t, err) - // Enable FIPS mode - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - - // Try to parse PKCS1 - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) + // Try to parse PKCS1 in FIPS mode + _, err = FIPS.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) require.Error(t, err) } diff --git a/cmd/swarm-rafttool/common.go b/cmd/swarm-rafttool/common.go index 532696d47b..a169b9af6e 100644 --- a/cmd/swarm-rafttool/common.go +++ b/cmd/swarm-rafttool/common.go @@ -75,9 +75,11 @@ func decryptRaftData(swarmdir, outdir, unlockKey string) error { return err } - _, d := encryption.Defaults(deks.CurrentDEK) + // always use false for FIPS, since we want to be able to decrypt logs written using + // any algorithm (not just FIPS-compatible ones) + _, d := encryption.Defaults(deks.CurrentDEK, false) if deks.PendingDEK == nil { - _, d2 := encryption.Defaults(deks.PendingDEK) + _, d2 := encryption.Defaults(deks.PendingDEK, false) d = encryption.NewMultiDecrypter(d, d2) } diff --git a/cmd/swarm-rafttool/common_test.go b/cmd/swarm-rafttool/common_test.go index ada57263fd..606110ad20 100644 --- a/cmd/swarm-rafttool/common_test.go +++ b/cmd/swarm-rafttool/common_test.go @@ -74,7 +74,7 @@ func TestDecrypt(t *testing.T) { Term: 1, }, } - e, d := encryption.Defaults(dek) + e, d := encryption.Defaults(dek, false) writeFakeRaftData(t, tempdir, &origSnapshot, storage.NewWALFactory(e, d), storage.NewSnapFactory(e, d)) outdir := filepath.Join(tempdir, "outdir") diff --git a/cmd/swarm-rafttool/dump.go b/cmd/swarm-rafttool/dump.go index e4e1196ecf..6360194d67 100644 --- a/cmd/swarm-rafttool/dump.go +++ b/cmd/swarm-rafttool/dump.go @@ -38,9 +38,11 @@ func loadData(swarmdir, unlockKey string) (*storage.WALData, *raftpb.Snapshot, e return nil, nil, err } - _, d := encryption.Defaults(deks.CurrentDEK) + // always set FIPS=false, because we want to decrypt logs stored using any + // algorithm, not just FIPS-compatible ones + _, d := encryption.Defaults(deks.CurrentDEK, false) if deks.PendingDEK == nil { - _, d2 := encryption.Defaults(deks.PendingDEK) + _, d2 := encryption.Defaults(deks.PendingDEK, false) d = encryption.NewMultiDecrypter(d, d2) } diff --git a/fips/fips.go b/fips/fips.go deleted file mode 100644 index 9fde7772ee..0000000000 --- a/fips/fips.go +++ /dev/null @@ -1,11 +0,0 @@ -package fips - -import "os" - -// EnvVar is the environment variable which stores FIPS mode state -const EnvVar = "GOFIPS" - -// Enabled returns true when FIPS mode is enabled -func Enabled() bool { - return os.Getenv(EnvVar) != "" -} diff --git a/manager/deks.go b/manager/deks.go index 4813a67d53..edb5227904 100644 --- a/manager/deks.go +++ b/manager/deks.go @@ -243,7 +243,7 @@ func (r *RaftDEKManager) MaybeUpdateKEK(candidateKEK ca.KEKData) (bool, bool, er func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { var decrypter encryption.Decrypter = encryption.NoopCrypter if kek != nil { - _, decrypter = encryption.Defaults(kek) + _, decrypter = encryption.Defaults(kek, false) } valueBytes, err := base64.StdEncoding.DecodeString(headerValue) if err != nil { @@ -259,7 +259,7 @@ func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { func encodePEMHeaderValue(headerValue []byte, kek []byte) (string, error) { var encrypter encryption.Encrypter = encryption.NoopCrypter if kek != nil { - encrypter, _ = encryption.Defaults(kek) + encrypter, _ = encryption.Defaults(kek, false) } encrypted, err := encryption.Encrypt(headerValue, encrypter) if err != nil { diff --git a/manager/encryption/encryption.go b/manager/encryption/encryption.go index 5b20f1ec8d..d9aad6ad84 100644 --- a/manager/encryption/encryption.go +++ b/manager/encryption/encryption.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/docker/swarmkit/api" - "github.com/docker/swarmkit/fips" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" ) @@ -150,10 +149,11 @@ func Encrypt(plaintext []byte, encrypter Encrypter) ([]byte, error) { return data, nil } -// Defaults returns a default encrypter and decrypter -func Defaults(key []byte) (Encrypter, Decrypter) { +// Defaults returns a default encrypter and decrypter. If the FIPS parameter is set to +// true, the only algorithm supported on both the encrypter and decrypter will be fernet. +func Defaults(key []byte, fips bool) (Encrypter, Decrypter) { f := NewFernet(key) - if fips.Enabled() { + if fips { return f, f } n := NewNACLSecretbox(key) diff --git a/manager/encryption/encryption_test.go b/manager/encryption/encryption_test.go index c974445e3a..2556ef8abe 100644 --- a/manager/encryption/encryption_test.go +++ b/manager/encryption/encryption_test.go @@ -2,11 +2,8 @@ package encryption import ( "fmt" - "os" "testing" - "github.com/docker/swarmkit/fips" - "github.com/stretchr/testify/require" ) @@ -32,7 +29,7 @@ func TestEncryptDecrypt(t *testing.T) { require.Equal(t, msg, decrypted) // the default encrypter can produce something the default decrypter can read - encrypter, decrypter := Defaults([]byte("key")) + encrypter, decrypter := Defaults([]byte("key"), false) encrypted, err = Encrypt(msg, encrypter) require.NoError(t, err) decrypted, err = Decrypt(encrypted, decrypter) @@ -117,23 +114,19 @@ func TestMultiDecryptor(t *testing.T) { // enabled, the encrypter/decrypter is Fernet only, because FIPS only permits // (given the algorithms swarmkit supports) AES-128-CBC func TestDefaults(t *testing.T) { - oldFipsVar := os.Getenv(fips.EnvVar) - plaintext := []byte("my message") - // ensure the fips var is not set - require.NoError(t, os.Unsetenv(fips.EnvVar)) - c, d := Defaults([]byte("key")) + // encrypt something without FIPS enabled + c, d := Defaults([]byte("key"), false) ciphertext, err := Encrypt(plaintext, c) require.NoError(t, err) decrypted, err := Decrypt(ciphertext, d) require.NoError(t, err) require.Equal(t, plaintext, decrypted) - // ensure that the fips var is set - defaults should return a fernet encrypter + // with fips enabled, defaults should return a fernet encrypter // and a decrypter that can't decrypt nacl - require.NoError(t, os.Setenv(fips.EnvVar, "true")) - c, d = Defaults([]byte("key")) + c, d = Defaults([]byte("key"), true) _, err = Decrypt(ciphertext, d) require.Error(t, err) ciphertext, err = Encrypt(plaintext, c) @@ -142,18 +135,10 @@ func TestDefaults(t *testing.T) { require.NoError(t, err) require.Equal(t, plaintext, decrypted) - // unset the fips var again, and ensure we can decrypt the previous ciphertext + // without FIPS, and ensure we can decrypt the previous ciphertext // (encrypted with fernet) with the decrypter returned by defaults - require.NoError(t, os.Unsetenv(fips.EnvVar)) - _, d = Defaults([]byte("key")) + _, d = Defaults([]byte("key"), false) decrypted, err = Decrypt(ciphertext, d) require.NoError(t, err) require.Equal(t, plaintext, decrypted) - - // put the env var back - if oldFipsVar == "" { - require.NoError(t, os.Unsetenv(fips.EnvVar)) - } else { - require.NoError(t, os.Setenv(fips.EnvVar, oldFipsVar)) - } } diff --git a/manager/manager_test.go b/manager/manager_test.go index e718c95f1c..3e15605ed7 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -366,7 +366,7 @@ func TestManagerLockUnlock(t *testing.T) { require.False(t, ok) // verify that the snapshot is readable with the new DEK - encrypter, decrypter := encryption.Defaults(currentDEK) + encrypter, decrypter := encryption.Defaults(currentDEK, false) // we can't use the raftLogger, because the WALs are still locked while the raft node is up. And once we remove // the manager, they'll be deleted. snapshot, err := storage.NewSnapFactory(encrypter, decrypter).New(filepath.Join(stateDir, "raft", "snap-v3-encrypted")).Load() diff --git a/manager/state/raft/storage/storage.go b/manager/state/raft/storage/storage.go index 764e5dbc16..bbd262f37c 100644 --- a/manager/state/raft/storage/storage.go +++ b/manager/state/raft/storage/storage.go @@ -38,6 +38,9 @@ type EncryptedRaftLogger struct { StateDir string EncryptionKey []byte + // FIPS specifies whether the encryption should be FIPS-compliant + FIPS bool + // mutex is locked for writing only when we need to replace the wal object and snapshotter // object, not when we're writing snapshots or wals (in which case it's locked for reading) encoderMu sync.RWMutex @@ -53,11 +56,11 @@ func (e *EncryptedRaftLogger) BootstrapFromDisk(ctx context.Context, oldEncrypti walDir := e.walDir() snapDir := e.snapDir() - encrypter, decrypter := encryption.Defaults(e.EncryptionKey) + encrypter, decrypter := encryption.Defaults(e.EncryptionKey, e.FIPS) if oldEncryptionKeys != nil { decrypters := []encryption.Decrypter{decrypter} for _, key := range oldEncryptionKeys { - _, d := encryption.Defaults(key) + _, d := encryption.Defaults(key, e.FIPS) decrypters = append(decrypters, d) } decrypter = encryption.NewMultiDecrypter(decrypters...) @@ -141,7 +144,7 @@ func (e *EncryptedRaftLogger) BootstrapFromDisk(ctx context.Context, oldEncrypti func (e *EncryptedRaftLogger) BootstrapNew(metadata []byte) error { e.encoderMu.Lock() defer e.encoderMu.Unlock() - encrypter, decrypter := encryption.Defaults(e.EncryptionKey) + encrypter, decrypter := encryption.Defaults(e.EncryptionKey, e.FIPS) walFactory := NewWALFactory(encrypter, decrypter) for _, dirpath := range []string{filepath.Dir(e.walDir()), e.snapDir()} { @@ -184,7 +187,7 @@ func (e *EncryptedRaftLogger) RotateEncryptionKey(newKey []byte) { panic(fmt.Errorf("EncryptedRaftLogger's WAL is not a wrappedWAL")) } - wrapped.encrypter, wrapped.decrypter = encryption.Defaults(newKey) + wrapped.encrypter, wrapped.decrypter = encryption.Defaults(newKey, e.FIPS) e.snapshotter = NewSnapFactory(wrapped.encrypter, wrapped.decrypter).New(e.snapDir()) } diff --git a/manager/state/raft/storage/storage_test.go b/manager/state/raft/storage/storage_test.go index 89db461578..f192eb48c2 100644 --- a/manager/state/raft/storage/storage_test.go +++ b/manager/state/raft/storage/storage_test.go @@ -184,7 +184,7 @@ func TestMigrateToV3EncryptedForm(t *testing.T) { v3EncryptedSnapshot.Metadata.Index += 200 v3EncryptedSnapshot.Metadata.Term += 20 - encoder, decoders := encryption.Defaults(dek) + encoder, decoders := encryption.Defaults(dek, false) walFactory := NewWALFactory(encoder, decoders) snapFactory := NewSnapFactory(encoder, decoders) From 48128f5190354587886e297a4f37ff1cb2f3f197 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Thu, 15 Mar 2018 15:55:58 -0700 Subject: [PATCH 07/15] Use the default keyutil formatter for tests, and remove fips checks for the root CA because we no longer support encrypting the root CA key, and PKCS8 vs PKCS1 only matters for fips if we encrypt. We want to keep the root key PKCS1 so that mixed version clusters will continue to work. Signed-off-by: Ying Li (cherry picked from commit 971930e9841e655cde3ed1393bd7e40b3d31c570) --- ca/certificates.go | 12 +----------- ca/certificates_test.go | 26 -------------------------- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/ca/certificates.go b/ca/certificates.go index 6bad7f9a9c..f2d3dbac55 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -26,10 +26,8 @@ import ( "github.com/cloudflare/cfssl/signer/local" "github.com/docker/go-events" "github.com/docker/swarmkit/api" - "github.com/docker/swarmkit/ca/keyutils" "github.com/docker/swarmkit/ca/pkcs8" "github.com/docker/swarmkit/connectionbroker" - "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/ioutils" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -636,7 +634,7 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo } // The key should not be encrypted, but it could be in PKCS8 format rather than PKCS1 - priv, err := keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, nil) + priv, err := helpers.ParsePrivateKeyPEM(keyBytes) if err != nil { return nil, errors.Wrap(err, "malformed private key") } @@ -782,14 +780,6 @@ func CreateRootCA(rootCN string) (RootCA, error) { return RootCA{}, err } - // Convert key to PKCS#8 in FIPS mode - if fips.Enabled() { - key, err = pkcs8.ConvertECPrivateKeyPEM(key) - if err != nil { - return RootCA{}, err - } - } - rootCA, err := NewRootCA(cert, cert, key, DefaultNodeCertExpiration, nil) if err != nil { return RootCA{}, err diff --git a/ca/certificates_test.go b/ca/certificates_test.go index 205534a6ef..2c7895510c 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -29,7 +29,6 @@ import ( "github.com/docker/swarmkit/ca" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/connectionbroker" - "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager/state" "github.com/docker/swarmkit/manager/state/store" @@ -78,31 +77,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestCreateRootCAKeyFormat(t *testing.T) { - // Check if the CA key generated is PKCS#1 when FIPS-mode is off - rootCA, err := ca.CreateRootCA("rootCA") - require.NoError(t, err) - - s, err := rootCA.Signer() - require.NoError(t, err) - block, _ := pem.Decode(s.Key) - require.NotNil(t, block) - require.Equal(t, "EC PRIVATE KEY", block.Type) - - // Check if the CA key generated is PKCS#8 when FIPS-mode is on - os.Setenv(fips.EnvVar, "1") - defer os.Unsetenv(fips.EnvVar) - - rootCA, err = ca.CreateRootCA("rootCA") - require.NoError(t, err) - - s, err = rootCA.Signer() - require.NoError(t, err) - block, _ = pem.Decode(s.Key) - require.NotNil(t, block) - require.Equal(t, "PRIVATE KEY", block.Type) -} - func TestCreateRootCASaveRootCA(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) From 790696f7a71b281e7770f03b8e8e0dc0aac7362c Mon Sep 17 00:00:00 2001 From: Ying Li Date: Thu, 15 Mar 2018 15:56:15 -0700 Subject: [PATCH 08/15] Add a key formatter parameter to the NewKeyReadWriter, and use that to encrypt and decrypt keys. It can be set using a setter function. Signed-off-by: Ying Li (cherry picked from commit 4ffb0ec22fdce25242ac835bf37b4c8d72523d15) --- ca/keyreadwriter.go | 33 ++++++++++++++++++---------- ca/keyreadwriter_test.go | 46 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/ca/keyreadwriter.go b/ca/keyreadwriter.go index c929523f38..cf4517fff4 100644 --- a/ca/keyreadwriter.go +++ b/ca/keyreadwriter.go @@ -73,21 +73,30 @@ func (e ErrInvalidKEK) Error() string { // KeyReadWriter is an object that knows how to read and write TLS keys and certs to disk, // optionally encrypted and optionally updating PEM headers. type KeyReadWriter struct { - mu sync.Mutex - kekData KEKData - paths CertPaths - headersObj PEMKeyHeaders + mu sync.Mutex + kekData KEKData + paths CertPaths + headersObj PEMKeyHeaders + keyFormatter keyutils.Formatter } // NewKeyReadWriter creates a new KeyReadWriter func NewKeyReadWriter(paths CertPaths, kek []byte, headersObj PEMKeyHeaders) *KeyReadWriter { return &KeyReadWriter{ - kekData: KEKData{KEK: kek}, - paths: paths, - headersObj: headersObj, + kekData: KEKData{KEK: kek}, + paths: paths, + headersObj: headersObj, + keyFormatter: keyutils.Default, } } +// SetKeyFormatter sets the keyformatter with which to encrypt and decrypt keys +func (k *KeyReadWriter) SetKeyFormatter(kf keyutils.Formatter) { + k.mu.Lock() + defer k.mu.Unlock() + k.keyFormatter = kf +} + // Migrate checks to see if a temporary key file exists. Older versions of // swarmkit wrote temporary keys instead of temporary certificates, so // migrate that temporary key if it exists. We want to write temporary certificates, @@ -324,8 +333,10 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) { return nil, ErrInvalidKEK{Wrapped: x509.IncorrectPasswordError} } - derBytes, err := keyutils.DecryptPEMBlock(keyBlock, k.kekData.KEK) - if err != nil { + derBytes, err := k.keyFormatter.DecryptPEMBlock(keyBlock, k.kekData.KEK) + if err == keyutils.ErrFIPSUnsupportedKeyFormat { + return nil, err + } else if err != nil { return nil, ErrInvalidKEK{Wrapped: err} } @@ -349,7 +360,7 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) { // writing it to disk. If the kek is nil, writes it to disk unencrypted. func (k *KeyReadWriter) writeKey(keyBlock *pem.Block, kekData KEKData, pkh PEMKeyHeaders) error { if kekData.KEK != nil { - encryptedPEMBlock, err := keyutils.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK) + encryptedPEMBlock, err := k.keyFormatter.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK) if err != nil { return err } @@ -404,7 +415,7 @@ func (k *KeyReadWriter) DowngradeKey() error { } if k.kekData.KEK != nil { - newBlock, err = keyutils.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK) + newBlock, err = k.keyFormatter.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK) if err != nil { return err } diff --git a/ca/keyreadwriter_test.go b/ca/keyreadwriter_test.go index 4b2610a74b..b28ee2c244 100644 --- a/ca/keyreadwriter_test.go +++ b/ca/keyreadwriter_test.go @@ -436,7 +436,7 @@ func testKeyReadWriterDowngradeKeyCase(t *testing.T, tc downgradeTestCase) error require.NotNil(t, block) kek = []byte("kek") - block, err = keyutils.EncryptPEMBlock(block.Bytes, kek) + block, err = keyutils.Default.EncryptPEMBlock(block.Bytes, kek) require.NoError(t, err) key = pem.EncodeToMemory(block) @@ -517,3 +517,47 @@ func TestKeyReadWriterDowngradeKey(t *testing.T) { require.NoError(t, err) } } + +// In FIPS mode, when reading a PKCS1 encrypted key, a PKCS1 error is returned as opposed +// to any other type of invalid KEK error +func TestKeyReadWriterReadNonFIPS(t *testing.T) { + t.Parallel() + cert, key, err := testutils.CreateRootCertAndKey("cn") + require.NoError(t, err) + + key, err = pkcs8.ConvertToECPrivateKeyPEM(key) + require.NoError(t, err) + + tempdir, err := ioutil.TempDir("", "KeyReadWriter") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created + + k := ca.NewKeyReadWriter(path.Node, nil, nil) + k.SetKeyFormatter(keyutils.FIPS) + + // can write an unencrypted PKCS1 key with no issues + require.NoError(t, k.Write(cert, key, nil)) + // can read the unencrypted key with no issues + readCert, readKey, err := k.Read() + require.NoError(t, err) + require.Equal(t, cert, readCert) + require.Equal(t, key, readKey) + + // cannot write an encrypted PKCS1 key + passphrase := []byte("passphrase") + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) + + k.SetKeyFormatter(keyutils.Default) + require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) + + // cannot read an encrypted PKCS1 key + k.SetKeyFormatter(keyutils.FIPS) + _, _, err = k.Read() + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, err) + + k.SetKeyFormatter(keyutils.Default) + _, _, err = k.Read() + require.NoError(t, err) +} From 45e58929f711283f7b928aee88ad176ae79928fe Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 27 Mar 2018 16:40:24 -0700 Subject: [PATCH 09/15] Add a FIPS bool to the node object, and propagate that boolean to the KeyReadWriter used in the node object. Signed-off-by: Ying Li (cherry picked from commit 43f607a2502dcfbc8576eaa3d9c60ef057c43d55) --- integration/cluster.go | 10 +++--- integration/integration_test.go | 39 +++++++-------------- integration/node.go | 3 +- node/node.go | 6 ++++ node/node_test.go | 62 +++++++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/integration/cluster.go b/integration/cluster.go index c08e7bbdaa..b36498b0f0 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -34,13 +34,14 @@ type testCluster struct { errs chan error wg sync.WaitGroup counter int + fips bool } var testnameKey struct{} // NewCluster creates new cluster to which nodes can be added. // AcceptancePolicy is set to most permissive mode on first manager node added. -func newTestCluster(testname string) *testCluster { +func newTestCluster(testname string, fips bool) *testCluster { ctx, cancel := context.WithCancel(context.Background()) ctx = context.WithValue(ctx, testnameKey, testname) c := &testCluster{ @@ -49,6 +50,7 @@ func newTestCluster(testname string) *testCluster { nodes: make(map[string]*testNode), nodesOrder: make(map[string]int), errs: make(chan error, 1024), + fips: fips, } c.api = &dummyAPI{c: c} return c @@ -92,7 +94,7 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { // first node var n *testNode if len(c.nodes) == 0 { - node, err := newTestNode("", "", lateBind) + node, err := newTestNode("", "", lateBind, c.fips) if err != nil { return err } @@ -113,7 +115,7 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { if err != nil { return err } - node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Manager, false) + node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Manager, false, c.fips) if err != nil { return err } @@ -169,7 +171,7 @@ func (c *testCluster) AddAgent() error { if err != nil { return err } - node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Worker, false) + node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Worker, false, c.fips) if err != nil { return err } diff --git a/integration/integration_test.go b/integration/integration_test.go index 8f1f2c29d5..81dc6f4000 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -21,7 +21,6 @@ import ( "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" cautils "github.com/docker/swarmkit/ca/testutils" - "github.com/docker/swarmkit/fips" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager" "github.com/docker/swarmkit/testutils" @@ -154,7 +153,7 @@ func pollServiceReady(t *testing.T, c *testCluster, sid string, replicas int) { } func newCluster(t *testing.T, numWorker, numManager int) *testCluster { - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(false, nil), "manager number %d", i+1) } @@ -166,8 +165,8 @@ func newCluster(t *testing.T, numWorker, numManager int) *testCluster { return cl } -func newClusterWithRootCA(t *testing.T, numWorker, numManager int, rootCA *ca.RootCA) *testCluster { - cl := newTestCluster(t.Name()) +func newClusterWithRootCA(t *testing.T, numWorker, numManager int, rootCA *ca.RootCA, fips bool) *testCluster { + cl := newTestCluster(t.Name(), fips) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(false, rootCA), "manager number %d", i+1) } @@ -194,7 +193,7 @@ func TestServiceCreateLateBind(t *testing.T) { numWorker, numManager := 3, 3 - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(true, nil), "manager number %d", i+1) } @@ -268,19 +267,12 @@ func TestNodeOps(t *testing.T) { func TestAutolockManagers(t *testing.T) { t.Parallel() - // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(fips.EnvVar) - for _, pkcs1 := range []bool{true, false} { - if pkcs1 { - os.Unsetenv(fips.EnvVar) - } else { - os.Setenv(fips.EnvVar, "1") - } - + // run this twice, once with FIPS set and once without FIPS set + for _, fips := range []bool{true, false} { rootCA, err := ca.CreateRootCA("rootCN") require.NoError(t, err) numWorker, numManager := 1, 1 - cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA) + cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA, fips) defer func() { require.NoError(t, cl.Stop()) }() @@ -551,7 +543,7 @@ func TestForceNewCluster(t *testing.T) { // start a new cluster with the external CA bootstrapped numWorker, numManager := 0, 1 - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) defer func() { require.NoError(t, cl.Stop()) }() @@ -621,20 +613,13 @@ func pollRootRotationDone(t *testing.T, cl *testCluster) { func TestSuccessfulRootRotation(t *testing.T) { t.Parallel() - // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(fips.EnvVar) - for _, pkcs1 := range []bool{true, false} { - if pkcs1 { - os.Unsetenv(fips.EnvVar) - } else { - os.Setenv(fips.EnvVar, "1") - } - + // run this twice, once with FIPS set and once without + for _, fips := range []bool{true, false} { rootCA, err := ca.CreateRootCA("rootCN") require.NoError(t, err) numWorker, numManager := 2, 3 - cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA) + cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA, fips) defer func() { require.NoError(t, cl.Stop()) }() @@ -858,7 +843,7 @@ func TestNodeJoinWithWrongCerts(t *testing.T) { require.NoError(t, err) for role, token := range tokens { - node, err := newTestNode(joinAddr, token, false) + node, err := newTestNode(joinAddr, token, false, false) require.NoError(t, err) nodeID := identity.NewID() require.NoError(t, diff --git a/integration/node.go b/integration/node.go index 9ea075ec6f..6b2100bfdf 100644 --- a/integration/node.go +++ b/integration/node.go @@ -54,7 +54,7 @@ func generateCerts(tmpDir string, rootCA *ca.RootCA, nodeID, role, org string, w // existing cluster. if joinAddr is empty string, then new cluster will be initialized. // It uses TestExecutor as executor. If lateBind is set, the remote API port is not // bound. If rootCA is set, this root is used to bootstrap the node's TLS certs. -func newTestNode(joinAddr, joinToken string, lateBind bool) (*testNode, error) { +func newTestNode(joinAddr, joinToken string, lateBind bool, fips bool) (*testNode, error) { tmpDir, err := ioutil.TempDir("", "swarmkit-integration-") if err != nil { return nil, err @@ -67,6 +67,7 @@ func newTestNode(joinAddr, joinToken string, lateBind bool) (*testNode, error) { StateDir: tmpDir, Executor: &agentutils.TestExecutor{}, JoinToken: joinToken, + FIPS: fips, } if !lateBind { cfg.ListenRemoteAPI = "127.0.0.1:0" diff --git a/node/node.go b/node/node.go index d7b6011207..d634cda827 100644 --- a/node/node.go +++ b/node/node.go @@ -14,6 +14,8 @@ import ( "sync" "time" + "github.com/docker/swarmkit/ca/keyutils" + "github.com/boltdb/bolt" "github.com/docker/docker/pkg/plugingetter" metrics "github.com/docker/go-metrics" @@ -667,6 +669,10 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP ) krw := ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{}) + // if FIPS is required, we want to make sure our key is stored in PKCS8 format + if n.config.FIPS { + krw.SetKeyFormatter(keyutils.FIPS) + } if err := krw.Migrate(); err != nil { return nil, nil, err } diff --git a/node/node_test.go b/node/node_test.go index 709b0d3d98..3c3d8d5b63 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -16,8 +16,10 @@ import ( agentutils "github.com/docker/swarmkit/agent/testutils" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/ca/keyutils" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/identity" + "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/state/store" "github.com/docker/swarmkit/testutils" "github.com/pkg/errors" @@ -25,6 +27,10 @@ import ( "golang.org/x/net/context" ) +func getLoggingContext(t *testing.T) context.Context { + return log.WithLogger(context.Background(), log.L.WithField("test", t.Name())) +} + // If there is nothing on disk and no join addr, we create a new CA and a new set of TLS certs. // If AutoLockManagers is enabled, the TLS key is encrypted with a randomly generated lock key. func TestLoadSecurityConfigNewNode(t *testing.T) { @@ -148,9 +154,9 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { require.Equal(t, ErrInvalidUnlockKey, err) // Invalid CA - rootCA, err = ca.CreateRootCA(ca.DefaultRootCN) + otherRootCA, err := ca.CreateRootCA(ca.DefaultRootCN) require.NoError(t, err) - require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA)) + require.NoError(t, ca.SaveRootCA(otherRootCA, paths.RootCA)) node, err = New(&Config{ StateDir: tempdir, JoinAddr: peer.Addr, @@ -160,6 +166,21 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { require.NoError(t, err) _, _, err = node.loadSecurityConfig(context.Background(), paths) require.IsType(t, x509.UnknownAuthorityError{}, errors.Cause(err)) + + // Convert to PKCS1 and require FIPS + require.NoError(t, krw.DowngradeKey()) + // go back to the previous root CA + require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA)) + node, err = New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tc.ManagerToken, + UnlockKey: []byte("passphrase"), + FIPS: true, + }) + require.NoError(t, err) + _, _, err = node.loadSecurityConfig(context.Background(), paths) + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, errors.Cause(err)) } // If there is no CA, and a join addr is provided, one is downloaded from the @@ -488,3 +509,40 @@ func TestManagerFailedStartup(t *testing.T) { require.EqualError(t, node.err, "manager stopped: can't initialize raft node: attempted to join raft cluster without knowing own address") } } + +// TestFIPSConfiguration ensures that new keys will be stored in PKCS8 format. +func TestFIPSConfiguration(t *testing.T) { + ctx := getLoggingContext(t) + tmpDir, err := ioutil.TempDir("", "fips") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + paths := ca.NewConfigPaths(filepath.Join(tmpDir, "certificates")) + + // don't bother with a listening socket + cAddr := filepath.Join(tmpDir, "control.sock") + cfg := &Config{ + ListenControlAPI: cAddr, + StateDir: tmpDir, + Executor: &agentutils.TestExecutor{}, + FIPS: true, + } + node, err := New(cfg) + require.NoError(t, err) + require.NoError(t, node.Start(ctx)) + defer func() { + require.NoError(t, node.Stop(ctx)) + }() + + select { + case <-node.Ready(): + case <-time.After(5 * time.Second): + require.FailNow(t, "node did not ready in time") + } + + nodeKey, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + pemBlock, _ := pem.Decode(nodeKey) + require.NotNil(t, pemBlock) + require.True(t, keyutils.IsPKCS8(pemBlock.Bytes)) +} From 02ae7c1f8cc64feb0f01da2707826c98ed3e5394 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 3 Apr 2018 12:05:34 -0700 Subject: [PATCH 10/15] Add a fips boolean to the node config. Signed-off-by: Ying Li (cherry picked from commit 6847b6c181f5fb4884ef7ede64656b5e2445689b) --- node/node.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/node.go b/node/node.go index d634cda827..3e37f869c9 100644 --- a/node/node.go +++ b/node/node.go @@ -125,6 +125,9 @@ type Config struct { // PluginGetter provides access to docker's plugin inventory. PluginGetter plugingetter.PluginGetter + + // FIPS is a boolean stating whether the node is FIPS enabled + FIPS bool } // Node implements the primary node functionality for a member of a swarm From c142f4f90de0d0669a5b3e948d33ab34ce39368c Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 6 Mar 2018 16:40:31 -0800 Subject: [PATCH 11/15] The agent now reports its FIPS status as part of the node description. Signed-off-by: Ying Li (cherry picked from commit 7c61addf579cb075d9cb7207ce17f38e36c8f8cd) --- agent/agent.go | 1 + agent/agent_test.go | 7 +- agent/config.go | 3 + api/api.pb.txt | 10 + api/types.pb.go | 673 +++++++++++++++++++++++--------------------- api/types.proto | 3 + node/node.go | 1 + 7 files changed, 379 insertions(+), 319 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index d28be94385..4a11c69e2f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -577,6 +577,7 @@ func (a *Agent) nodeDescriptionWithHostname(ctx context.Context, tlsInfo *api.No desc.Hostname = a.config.Hostname } desc.TLSInfo = tlsInfo + desc.FIPS = a.config.FIPS } return desc, err } diff --git a/agent/agent_test.go b/agent/agent_test.go index 9dc1a0c4cf..005c65eb8b 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -232,17 +232,20 @@ func TestHandleSessionMessageNodeChanges(t *testing.T) { require.Empty(t, closedSessions) } -// when the node description changes, the session is restarted and propagated up to the dispatcher +// when the node description changes, the session is restarted and propagated up to the dispatcher. +// the node description includes the FIPSness of the agent. func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { tlsCh := make(chan events.Event, 1) defer close(tlsCh) tester := agentTestEnv(t, nil, tlsCh) + tester.agent.config.FIPS = true // start out with the agent in FIPS-enabled mode defer tester.cleanup() defer tester.StartAgent(t)() currSession, closedSessions := tester.dispatcher.GetSessions() require.NotNil(t, currSession) require.NotNil(t, currSession.Description) + require.True(t, currSession.Description.FIPS) require.Empty(t, closedSessions) tester.executor.UpdateNodeDescription(&api.NodeDescription{ @@ -262,6 +265,7 @@ func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { require.NotEqual(t, currSession, gotSession) require.NotNil(t, gotSession.Description) require.Equal(t, "testAgent", gotSession.Description.Hostname) + require.True(t, gotSession.Description.FIPS) currSession = gotSession // If nothing changes, the session is not re-established @@ -291,6 +295,7 @@ func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { require.NotNil(t, gotSession.Description) require.Equal(t, "testAgent", gotSession.Description.Hostname) require.Equal(t, newTLSInfo, gotSession.Description.TLSInfo) + require.True(t, gotSession.Description.FIPS) } // If the dispatcher returns an error, if it times out, or if it's unreachable, no matter diff --git a/agent/config.go b/agent/config.go index 98d5bf5728..b4293e9e03 100644 --- a/agent/config.go +++ b/agent/config.go @@ -47,6 +47,9 @@ type Config struct { // SessionTracker, if provided, will have its SessionClosed and SessionError methods called // when sessions close and error. SessionTracker SessionTracker + + // FIPS returns whether the node is FIPS-enabled + FIPS bool } func (c *Config) validate() error { diff --git a/api/api.pb.txt b/api/api.pb.txt index 251e262720..5441cdb4a4 100755 --- a/api/api.pb.txt +++ b/api/api.pb.txt @@ -2171,6 +2171,16 @@ file { } json_name: "tlsInfo" } + field { + name: "fips" + number: 6 + label: LABEL_OPTIONAL + type: TYPE_BOOL + options { + 65004: "FIPS" + } + json_name: "fips" + } } message_type { name: "NodeTLSInfo" diff --git a/api/types.pb.go b/api/types.pb.go index 3f583261b6..80ab4a646d 100644 --- a/api/types.pb.go +++ b/api/types.pb.go @@ -853,6 +853,8 @@ type NodeDescription struct { Engine *EngineDescription `protobuf:"bytes,4,opt,name=engine" json:"engine,omitempty"` // Information on the node's TLS setup TLSInfo *NodeTLSInfo `protobuf:"bytes,5,opt,name=tls_info,json=tlsInfo" json:"tls_info,omitempty"` + // FIPS indicates whether the node has FIPS-enabled + FIPS bool `protobuf:"varint,6,opt,name=fips,proto3" json:"fips,omitempty"` } func (m *NodeDescription) Reset() { *m = NodeDescription{} } @@ -4005,6 +4007,16 @@ func (m *NodeDescription) MarshalTo(dAtA []byte) (int, error) { } i += n9 } + if m.FIPS { + dAtA[i] = 0x30 + i++ + if m.FIPS { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -6364,6 +6376,9 @@ func (m *NodeDescription) Size() (n int) { l = m.TLSInfo.Size() n += 1 + l + sovTypes(uint64(l)) } + if m.FIPS { + n += 2 + } return n } @@ -7498,6 +7513,7 @@ func (this *NodeDescription) String() string { `Resources:` + strings.Replace(fmt.Sprintf("%v", this.Resources), "Resources", "Resources", 1) + `,`, `Engine:` + strings.Replace(fmt.Sprintf("%v", this.Engine), "EngineDescription", "EngineDescription", 1) + `,`, `TLSInfo:` + strings.Replace(fmt.Sprintf("%v", this.TLSInfo), "NodeTLSInfo", "NodeTLSInfo", 1) + `,`, + `FIPS:` + fmt.Sprintf("%v", this.FIPS) + `,`, `}`, }, "") return s @@ -9814,6 +9830,26 @@ func (m *NodeDescription) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FIPS", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FIPS = bool(v != 0) default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -17046,324 +17082,325 @@ var ( func init() { proto.RegisterFile("github.com/docker/swarmkit/api/types.proto", fileDescriptorTypes) } var fileDescriptorTypes = []byte{ - // 5091 bytes of a gzipped FileDescriptorProto + // 5116 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x7a, 0x5d, 0x6c, 0x23, 0x59, - 0x56, 0x7f, 0xec, 0xd8, 0x8e, 0x7d, 0xec, 0x24, 0xd5, 0xb7, 0xb3, 0x3d, 0x69, 0x6f, 0x4f, 0x92, + 0x56, 0x7f, 0xec, 0xd8, 0x8e, 0x7d, 0xec, 0x24, 0xd5, 0xb7, 0xb3, 0x3d, 0x69, 0x6f, 0x4f, 0xe2, 0xa9, 0x99, 0xde, 0x99, 0xed, 0x9d, 0xbf, 0xfb, 0x6b, 0x77, 0xd5, 0x33, 0xf3, 0xdf, 0x9d, 0xb1, - 0xcb, 0x95, 0x8e, 0xb7, 0x13, 0xdb, 0xba, 0x76, 0xba, 0x77, 0x91, 0xa0, 0xa8, 0x54, 0xdd, 0x38, - 0x35, 0x29, 0xd7, 0x35, 0x55, 0xe5, 0xa4, 0xcd, 0x82, 0x18, 0xf1, 0x00, 0x28, 0x4f, 0xf0, 0x02, - 0x8b, 0x50, 0x10, 0x12, 0xbc, 0x81, 0xc4, 0x03, 0x48, 0x08, 0x9e, 0x06, 0x09, 0xa1, 0x7d, 0x83, - 0x05, 0x09, 0xad, 0x40, 0x0a, 0x6c, 0x1e, 0x78, 0x5b, 0xc1, 0x0b, 0xe2, 0x85, 0x07, 0x74, 0x3f, - 0xaa, 0x5c, 0x71, 0x57, 0x92, 0x19, 0x76, 0x5f, 0x12, 0xdf, 0x73, 0x7e, 0xe7, 0xdc, 0x7b, 0xcf, - 0xbd, 0xf7, 0xdc, 0x73, 0xce, 0x2d, 0xb8, 0x37, 0x70, 0xc2, 0x83, 0xf1, 0x5e, 0xcd, 0xa2, 0xc3, - 0xfb, 0x36, 0xb5, 0x0e, 0x89, 0x7f, 0x3f, 0x38, 0x36, 0xfd, 0xe1, 0xa1, 0x13, 0xde, 0x37, 0x47, - 0xce, 0xfd, 0x70, 0x32, 0x22, 0x41, 0x6d, 0xe4, 0xd3, 0x90, 0x22, 0x24, 0x00, 0xb5, 0x08, 0x50, - 0x3b, 0x7a, 0x58, 0x5d, 0x1f, 0x50, 0x3a, 0x70, 0xc9, 0x7d, 0x8e, 0xd8, 0x1b, 0xef, 0xdf, 0x0f, - 0x9d, 0x21, 0x09, 0x42, 0x73, 0x38, 0x12, 0x42, 0xd5, 0xb5, 0x59, 0x80, 0x3d, 0xf6, 0xcd, 0xd0, - 0xa1, 0x9e, 0xe4, 0xaf, 0x0c, 0xe8, 0x80, 0xf2, 0x9f, 0xf7, 0xd9, 0x2f, 0x41, 0x55, 0xd7, 0x61, - 0xe1, 0x39, 0xf1, 0x03, 0x87, 0x7a, 0x68, 0x05, 0xf2, 0x8e, 0x67, 0x93, 0x97, 0xab, 0x99, 0x8d, - 0xcc, 0x3b, 0x39, 0x2c, 0x1a, 0xea, 0x03, 0x80, 0x16, 0xfb, 0xa1, 0x7b, 0xa1, 0x3f, 0x41, 0x0a, - 0xcc, 0x1f, 0x92, 0x09, 0x47, 0x94, 0x30, 0xfb, 0xc9, 0x28, 0x47, 0xa6, 0xbb, 0x9a, 0x15, 0x94, - 0x23, 0xd3, 0x55, 0x7f, 0x94, 0x81, 0x72, 0xdd, 0xf3, 0x68, 0xc8, 0x7b, 0x0f, 0x10, 0x82, 0x9c, - 0x67, 0x0e, 0x89, 0x14, 0xe2, 0xbf, 0x91, 0x06, 0x05, 0xd7, 0xdc, 0x23, 0x6e, 0xb0, 0x9a, 0xdd, - 0x98, 0x7f, 0xa7, 0xfc, 0xe8, 0x2b, 0xb5, 0x57, 0xa7, 0x5c, 0x4b, 0x28, 0xa9, 0x6d, 0x73, 0x34, - 0x1f, 0x04, 0x96, 0xa2, 0xe8, 0x9b, 0xb0, 0xe0, 0x78, 0xb6, 0x63, 0x91, 0x60, 0x35, 0xc7, 0xb5, - 0xac, 0xa5, 0x69, 0x99, 0x8e, 0xbe, 0x91, 0xfb, 0xfe, 0xd9, 0xfa, 0x1c, 0x8e, 0x84, 0xaa, 0xef, - 0x41, 0x39, 0xa1, 0x36, 0x65, 0x6e, 0x2b, 0x90, 0x3f, 0x32, 0xdd, 0x31, 0x91, 0xb3, 0x13, 0x8d, - 0xf7, 0xb3, 0x4f, 0x32, 0xea, 0x47, 0xb0, 0xd2, 0x36, 0x87, 0xc4, 0x7e, 0x4a, 0x3c, 0xe2, 0x3b, - 0x16, 0x26, 0x01, 0x1d, 0xfb, 0x16, 0x61, 0x73, 0x3d, 0x74, 0x3c, 0x3b, 0x9a, 0x2b, 0xfb, 0x9d, - 0xae, 0x45, 0xd5, 0xe0, 0xb5, 0xa6, 0x13, 0x58, 0x3e, 0x09, 0xc9, 0xe7, 0x56, 0x32, 0x1f, 0x29, - 0x39, 0xcb, 0xc0, 0xf2, 0xac, 0xf4, 0xcf, 0xc0, 0x4d, 0x66, 0x62, 0xdb, 0xf0, 0x25, 0xc5, 0x08, - 0x46, 0xc4, 0xe2, 0xca, 0xca, 0x8f, 0xde, 0x49, 0xb3, 0x50, 0xda, 0x4c, 0xb6, 0xe6, 0xf0, 0x0d, - 0xae, 0x26, 0x22, 0xf4, 0x46, 0xc4, 0x42, 0x16, 0xdc, 0xb2, 0xe5, 0xa0, 0x67, 0xd4, 0x67, 0xb9, - 0xfa, 0xd4, 0x65, 0xbc, 0x64, 0x9a, 0x5b, 0x73, 0x78, 0x25, 0x52, 0x96, 0xec, 0xa4, 0x01, 0x50, - 0x8c, 0x74, 0xab, 0xdf, 0xcb, 0x40, 0x29, 0x62, 0x06, 0xe8, 0xcb, 0x50, 0xf2, 0x4c, 0x8f, 0x1a, - 0xd6, 0x68, 0x1c, 0xf0, 0x09, 0xcd, 0x37, 0x2a, 0xe7, 0x67, 0xeb, 0xc5, 0xb6, 0xe9, 0x51, 0xad, - 0xbb, 0x1b, 0xe0, 0x22, 0x63, 0x6b, 0xa3, 0x71, 0x80, 0xde, 0x80, 0xca, 0x90, 0x0c, 0xa9, 0x3f, - 0x31, 0xf6, 0x26, 0x21, 0x09, 0xa4, 0xd9, 0xca, 0x82, 0xd6, 0x60, 0x24, 0xf4, 0x0d, 0x58, 0x18, - 0x88, 0x21, 0xad, 0xce, 0xf3, 0xed, 0xf3, 0x66, 0xda, 0xe8, 0x67, 0x46, 0x8d, 0x23, 0x19, 0xf5, - 0x37, 0x33, 0xb0, 0x12, 0x53, 0xc9, 0x2f, 0x8c, 0x1d, 0x9f, 0x0c, 0x89, 0x17, 0x06, 0xe8, 0x6b, - 0x50, 0x70, 0x9d, 0xa1, 0x13, 0x06, 0xd2, 0xe6, 0xaf, 0xa7, 0xa9, 0x8d, 0x27, 0x85, 0x25, 0x18, - 0xd5, 0xa1, 0xe2, 0x93, 0x80, 0xf8, 0x47, 0x62, 0xc7, 0x4b, 0x8b, 0x5e, 0x23, 0x7c, 0x41, 0x44, - 0xdd, 0x84, 0x62, 0xd7, 0x35, 0xc3, 0x7d, 0xea, 0x0f, 0x91, 0x0a, 0x15, 0xd3, 0xb7, 0x0e, 0x9c, - 0x90, 0x58, 0xe1, 0xd8, 0x8f, 0x4e, 0xdf, 0x05, 0x1a, 0xba, 0x05, 0x59, 0x2a, 0x3a, 0x2a, 0x35, - 0x0a, 0xe7, 0x67, 0xeb, 0xd9, 0x4e, 0x0f, 0x67, 0x69, 0xa0, 0x7e, 0x00, 0x37, 0xba, 0xee, 0x78, - 0xe0, 0x78, 0x4d, 0x12, 0x58, 0xbe, 0x33, 0x62, 0xda, 0xd9, 0xae, 0x64, 0x3e, 0x2a, 0xda, 0x95, - 0xec, 0x77, 0x7c, 0xb4, 0xb3, 0xd3, 0xa3, 0xad, 0xfe, 0x7a, 0x16, 0x6e, 0xe8, 0xde, 0xc0, 0xf1, - 0x48, 0x52, 0xfa, 0x2e, 0x2c, 0x11, 0x4e, 0x34, 0x8e, 0x84, 0xbb, 0x91, 0x7a, 0x16, 0x05, 0x35, - 0xf2, 0x41, 0xad, 0x19, 0xbf, 0xf0, 0x30, 0x6d, 0xfa, 0xaf, 0x68, 0x4f, 0xf5, 0x0e, 0x3a, 0x2c, - 0x8c, 0xf8, 0x24, 0x02, 0xb9, 0xbc, 0x77, 0xd3, 0x74, 0xbd, 0x32, 0xcf, 0xc8, 0x49, 0x48, 0xd9, - 0x9f, 0xc4, 0x49, 0xfc, 0x49, 0x16, 0x96, 0xdb, 0xd4, 0xbe, 0x60, 0x87, 0x2a, 0x14, 0x0f, 0x68, - 0x10, 0x26, 0x1c, 0x62, 0xdc, 0x46, 0x4f, 0xa0, 0x38, 0x92, 0xcb, 0x27, 0x57, 0xff, 0x4e, 0xfa, - 0x90, 0x05, 0x06, 0xc7, 0x68, 0xf4, 0x01, 0x94, 0xa2, 0x23, 0xc3, 0x66, 0xfb, 0x19, 0x36, 0xce, - 0x14, 0x8f, 0xbe, 0x01, 0x05, 0xb1, 0x08, 0xab, 0x39, 0x2e, 0x79, 0xf7, 0x33, 0xd9, 0x1c, 0x4b, - 0x21, 0xf4, 0x14, 0x8a, 0xa1, 0x1b, 0x18, 0x8e, 0xb7, 0x4f, 0x57, 0xf3, 0x5c, 0xc1, 0x7a, 0xaa, - 0x93, 0xa1, 0x36, 0xe9, 0x6f, 0xf7, 0x5a, 0xde, 0x3e, 0x6d, 0x94, 0xcf, 0xcf, 0xd6, 0x17, 0x64, - 0x03, 0x2f, 0x84, 0x6e, 0xc0, 0x7e, 0xa8, 0xbf, 0x95, 0x81, 0x72, 0x02, 0x85, 0x5e, 0x07, 0x08, - 0xfd, 0x71, 0x10, 0x1a, 0x3e, 0xa5, 0x21, 0x37, 0x56, 0x05, 0x97, 0x38, 0x05, 0x53, 0x1a, 0xa2, - 0x1a, 0xdc, 0xb4, 0x88, 0x1f, 0x1a, 0x4e, 0x10, 0x8c, 0x89, 0x6f, 0x04, 0xe3, 0xbd, 0x8f, 0x89, - 0x15, 0x72, 0xc3, 0x55, 0xf0, 0x0d, 0xc6, 0x6a, 0x71, 0x4e, 0x4f, 0x30, 0xd0, 0x63, 0xb8, 0x95, - 0xc4, 0x8f, 0xc6, 0x7b, 0xae, 0x63, 0x19, 0x6c, 0x31, 0xe7, 0xb9, 0xc8, 0xcd, 0xa9, 0x48, 0x97, - 0xf3, 0x9e, 0x91, 0x89, 0xfa, 0xc3, 0x0c, 0x28, 0xd8, 0xdc, 0x0f, 0x77, 0xc8, 0x70, 0x8f, 0xf8, - 0xbd, 0xd0, 0x0c, 0xc7, 0x01, 0xba, 0x05, 0x05, 0x97, 0x98, 0x36, 0xf1, 0xf9, 0xa0, 0x8a, 0x58, - 0xb6, 0xd0, 0x2e, 0x3b, 0xc1, 0xa6, 0x75, 0x60, 0xee, 0x39, 0xae, 0x13, 0x4e, 0xf8, 0x50, 0x96, - 0xd2, 0xb7, 0xf0, 0xac, 0xce, 0x1a, 0x4e, 0x08, 0xe2, 0x0b, 0x6a, 0xd0, 0x2a, 0x2c, 0x0c, 0x49, - 0x10, 0x98, 0x03, 0xc2, 0x47, 0x5a, 0xc2, 0x51, 0x53, 0xfd, 0x00, 0x2a, 0x49, 0x39, 0x54, 0x86, - 0x85, 0xdd, 0xf6, 0xb3, 0x76, 0xe7, 0x45, 0x5b, 0x99, 0x43, 0xcb, 0x50, 0xde, 0x6d, 0x63, 0xbd, - 0xae, 0x6d, 0xd5, 0x1b, 0xdb, 0xba, 0x92, 0x41, 0x8b, 0x50, 0x9a, 0x36, 0xb3, 0xea, 0x9f, 0x65, - 0x00, 0x98, 0xb9, 0xe5, 0xa4, 0xde, 0x87, 0x7c, 0x10, 0x9a, 0xa1, 0xd8, 0x95, 0x4b, 0x8f, 0xde, - 0xba, 0x6c, 0x0d, 0xe5, 0x78, 0xd9, 0x3f, 0x82, 0x85, 0x48, 0x72, 0x84, 0xd9, 0x0b, 0x23, 0x64, - 0x0e, 0xc2, 0xb4, 0x6d, 0x5f, 0x0e, 0x9c, 0xff, 0x56, 0x3f, 0x80, 0x3c, 0x97, 0xbe, 0x38, 0xdc, - 0x22, 0xe4, 0x9a, 0xec, 0x57, 0x06, 0x95, 0x20, 0x8f, 0xf5, 0x7a, 0xf3, 0x3b, 0x4a, 0x16, 0x29, - 0x50, 0x69, 0xb6, 0x7a, 0x5a, 0xa7, 0xdd, 0xd6, 0xb5, 0xbe, 0xde, 0x54, 0xe6, 0xd5, 0xbb, 0x90, - 0x6f, 0x0d, 0x99, 0xe6, 0x3b, 0x6c, 0xcb, 0xef, 0x13, 0x9f, 0x78, 0x56, 0x74, 0x92, 0xa6, 0x04, - 0xf5, 0xc7, 0x65, 0xc8, 0xef, 0xd0, 0xb1, 0x17, 0xa2, 0x47, 0x09, 0xb7, 0xb5, 0x94, 0x1e, 0x21, - 0x70, 0x60, 0xad, 0x3f, 0x19, 0x11, 0xe9, 0xd6, 0x6e, 0x41, 0x41, 0x1c, 0x0e, 0x39, 0x1d, 0xd9, - 0x62, 0xf4, 0xd0, 0xf4, 0x07, 0x24, 0x94, 0xf3, 0x91, 0x2d, 0xf4, 0x0e, 0xbb, 0xb1, 0x4c, 0x9b, - 0x7a, 0xee, 0x84, 0x9f, 0xa1, 0xa2, 0xb8, 0x96, 0x30, 0x31, 0xed, 0x8e, 0xe7, 0x4e, 0x70, 0xcc, - 0x45, 0x5b, 0x50, 0xd9, 0x73, 0x3c, 0xdb, 0xa0, 0x23, 0xe1, 0xe4, 0xf3, 0x97, 0x9f, 0x38, 0x31, - 0xaa, 0x86, 0xe3, 0xd9, 0x1d, 0x01, 0xc6, 0xe5, 0xbd, 0x69, 0x03, 0xb5, 0x61, 0xe9, 0x88, 0xba, - 0xe3, 0x21, 0x89, 0x75, 0x15, 0xb8, 0xae, 0xb7, 0x2f, 0xd7, 0xf5, 0x9c, 0xe3, 0x23, 0x6d, 0x8b, - 0x47, 0xc9, 0x26, 0x7a, 0x06, 0x8b, 0xe1, 0x70, 0xb4, 0x1f, 0xc4, 0xea, 0x16, 0xb8, 0xba, 0x2f, - 0x5d, 0x61, 0x30, 0x06, 0x8f, 0xb4, 0x55, 0xc2, 0x44, 0x0b, 0x3d, 0x85, 0xb2, 0x45, 0xbd, 0xc0, - 0x09, 0x42, 0xe2, 0x59, 0x93, 0xd5, 0x22, 0xb7, 0xfd, 0x15, 0xb3, 0xd4, 0xa6, 0x60, 0x9c, 0x94, - 0xac, 0xfe, 0xea, 0x3c, 0x94, 0x13, 0x26, 0x40, 0x3d, 0x28, 0x8f, 0x7c, 0x3a, 0x32, 0x07, 0xfc, - 0xc6, 0x93, 0x8b, 0xfa, 0xf0, 0x33, 0x99, 0xaf, 0xd6, 0x9d, 0x0a, 0xe2, 0xa4, 0x16, 0xf5, 0x34, - 0x0b, 0xe5, 0x04, 0x13, 0xdd, 0x83, 0x22, 0xee, 0xe2, 0xd6, 0xf3, 0x7a, 0x5f, 0x57, 0xe6, 0xaa, - 0x77, 0x4e, 0x4e, 0x37, 0x56, 0xb9, 0xb6, 0xa4, 0x82, 0xae, 0xef, 0x1c, 0xb1, 0x3d, 0xfc, 0x0e, - 0x2c, 0x44, 0xd0, 0x4c, 0xf5, 0x8b, 0x27, 0xa7, 0x1b, 0xaf, 0xcd, 0x42, 0x13, 0x48, 0xdc, 0xdb, - 0xaa, 0x63, 0xbd, 0xa9, 0x64, 0xd3, 0x91, 0xb8, 0x77, 0x60, 0xfa, 0xc4, 0x46, 0x5f, 0x82, 0x82, - 0x04, 0xce, 0x57, 0xab, 0x27, 0xa7, 0x1b, 0xb7, 0x66, 0x81, 0x53, 0x1c, 0xee, 0x6d, 0xd7, 0x9f, - 0xeb, 0x4a, 0x2e, 0x1d, 0x87, 0x7b, 0xae, 0x79, 0x44, 0xd0, 0x5b, 0x90, 0x17, 0xb0, 0x7c, 0xf5, - 0xf6, 0xc9, 0xe9, 0xc6, 0x17, 0x5e, 0x51, 0xc7, 0x50, 0xd5, 0xd5, 0xdf, 0xf8, 0xc3, 0xb5, 0xb9, - 0xbf, 0xfa, 0xa3, 0x35, 0x65, 0x96, 0x5d, 0xfd, 0x9f, 0x0c, 0x2c, 0x5e, 0xd8, 0x3b, 0x48, 0x85, - 0x82, 0x47, 0x2d, 0x3a, 0x12, 0x17, 0x61, 0xb1, 0x01, 0xe7, 0x67, 0xeb, 0x85, 0x36, 0xd5, 0xe8, - 0x68, 0x82, 0x25, 0x07, 0x3d, 0x9b, 0xb9, 0xca, 0x1f, 0x7f, 0xc6, 0x8d, 0x99, 0x7a, 0x99, 0x7f, - 0x08, 0x8b, 0xb6, 0xef, 0x1c, 0x11, 0xdf, 0xb0, 0xa8, 0xb7, 0xef, 0x0c, 0xe4, 0x25, 0x57, 0x4d, - 0x8d, 0x37, 0x39, 0x10, 0x57, 0x84, 0x80, 0xc6, 0xf1, 0x3f, 0xc1, 0x35, 0x5e, 0x7d, 0x0e, 0x95, - 0xe4, 0x56, 0x67, 0xf7, 0x52, 0xe0, 0xfc, 0x22, 0x91, 0x81, 0x25, 0x0f, 0x43, 0x71, 0x89, 0x51, - 0x44, 0x58, 0xf9, 0x36, 0xe4, 0x86, 0xd4, 0x16, 0x7a, 0x16, 0x1b, 0x37, 0x59, 0x34, 0xf1, 0xcf, - 0x67, 0xeb, 0x65, 0x1a, 0xd4, 0x36, 0x1d, 0x97, 0xec, 0x50, 0x9b, 0x60, 0x0e, 0x50, 0x8f, 0x20, - 0xc7, 0x7c, 0x0e, 0xfa, 0x22, 0xe4, 0x1a, 0xad, 0x76, 0x53, 0x99, 0xab, 0xde, 0x38, 0x39, 0xdd, - 0x58, 0xe4, 0x26, 0x61, 0x0c, 0xb6, 0x77, 0xd1, 0x3a, 0x14, 0x9e, 0x77, 0xb6, 0x77, 0x77, 0xd8, - 0xf6, 0xba, 0x79, 0x72, 0xba, 0xb1, 0x1c, 0xb3, 0x85, 0xd1, 0xd0, 0xeb, 0x90, 0xef, 0xef, 0x74, - 0x37, 0x7b, 0x4a, 0xb6, 0x8a, 0x4e, 0x4e, 0x37, 0x96, 0x62, 0x3e, 0x1f, 0x73, 0xf5, 0x86, 0x5c, - 0xd5, 0x52, 0x4c, 0x57, 0x7f, 0x90, 0x81, 0x72, 0xe2, 0xc0, 0xb1, 0x8d, 0xd9, 0xd4, 0x37, 0xeb, - 0xbb, 0xdb, 0x7d, 0x65, 0x2e, 0xb1, 0x31, 0x13, 0x90, 0x26, 0xd9, 0x37, 0xc7, 0x2e, 0xf3, 0x73, - 0xa0, 0x75, 0xda, 0xbd, 0x56, 0xaf, 0xaf, 0xb7, 0xfb, 0x4a, 0xa6, 0xba, 0x7a, 0x72, 0xba, 0xb1, - 0x32, 0x0b, 0xde, 0x1c, 0xbb, 0x2e, 0xdb, 0x9a, 0x5a, 0x5d, 0xdb, 0xe2, 0x7b, 0x7d, 0xba, 0x35, - 0x13, 0x28, 0xcd, 0xb4, 0x0e, 0x88, 0x8d, 0xde, 0x85, 0x52, 0x53, 0xdf, 0xd6, 0x9f, 0xd6, 0xb9, - 0x77, 0xaf, 0xbe, 0x7e, 0x72, 0xba, 0x71, 0xfb, 0xd5, 0xde, 0x5d, 0x32, 0x30, 0x43, 0x62, 0xcf, - 0x6c, 0xd1, 0x04, 0x44, 0xfd, 0xaf, 0x2c, 0x2c, 0x62, 0x96, 0x0e, 0xfb, 0x61, 0x97, 0xba, 0x8e, - 0x35, 0x41, 0x5d, 0x28, 0x59, 0xd4, 0xb3, 0x9d, 0x84, 0x9f, 0x78, 0x74, 0x49, 0x48, 0x34, 0x95, - 0x8a, 0x5a, 0x5a, 0x24, 0x89, 0xa7, 0x4a, 0xd0, 0x7d, 0xc8, 0xdb, 0xc4, 0x35, 0x27, 0x32, 0x36, - 0xbb, 0x5d, 0x13, 0x09, 0x77, 0x2d, 0x4a, 0xb8, 0x6b, 0x4d, 0x99, 0x70, 0x63, 0x81, 0xe3, 0x39, - 0x88, 0xf9, 0xd2, 0x30, 0xc3, 0x90, 0x0c, 0x47, 0xa1, 0x08, 0xcc, 0x72, 0xb8, 0x3c, 0x34, 0x5f, - 0xd6, 0x25, 0x09, 0x3d, 0x84, 0xc2, 0xb1, 0xe3, 0xd9, 0xf4, 0x58, 0xc6, 0x5e, 0x57, 0x28, 0x95, - 0x40, 0xf5, 0x84, 0x85, 0x24, 0x33, 0xc3, 0x64, 0x7b, 0xa8, 0xdd, 0x69, 0xeb, 0xd1, 0x1e, 0x92, - 0xfc, 0x8e, 0xd7, 0xa6, 0x1e, 0x3b, 0xff, 0xd0, 0x69, 0x1b, 0x9b, 0xf5, 0xd6, 0xf6, 0x2e, 0x66, - 0xfb, 0x68, 0xe5, 0xe4, 0x74, 0x43, 0x89, 0x21, 0x9b, 0xa6, 0xe3, 0xb2, 0x64, 0xe0, 0x36, 0xcc, - 0xd7, 0xdb, 0xdf, 0x51, 0xb2, 0x55, 0xe5, 0xe4, 0x74, 0xa3, 0x12, 0xb3, 0xeb, 0xde, 0x64, 0x6a, - 0xf7, 0xd9, 0x7e, 0xd5, 0xbf, 0x9b, 0x87, 0xca, 0xee, 0xc8, 0x36, 0x43, 0x22, 0xce, 0x19, 0xda, - 0x80, 0xf2, 0xc8, 0xf4, 0x4d, 0xd7, 0x25, 0xae, 0x13, 0x0c, 0x65, 0x29, 0x21, 0x49, 0x42, 0xef, - 0x7d, 0x56, 0x33, 0x36, 0x8a, 0xec, 0xec, 0x7c, 0xef, 0x5f, 0xd7, 0x33, 0x91, 0x41, 0x77, 0x61, - 0x69, 0x5f, 0x8c, 0xd6, 0x30, 0x2d, 0xbe, 0xb0, 0xf3, 0x7c, 0x61, 0x6b, 0x69, 0x0b, 0x9b, 0x1c, - 0x56, 0x4d, 0x4e, 0xb2, 0xce, 0xa5, 0xf0, 0xe2, 0x7e, 0xb2, 0x89, 0x1e, 0xc3, 0xc2, 0x90, 0x7a, - 0x4e, 0x48, 0xfd, 0xeb, 0x57, 0x21, 0x42, 0xa2, 0x7b, 0x70, 0x83, 0x2d, 0x6e, 0x34, 0x1e, 0xce, - 0xe6, 0xd7, 0x79, 0x16, 0x2f, 0x0f, 0xcd, 0x97, 0xb2, 0x43, 0xcc, 0xc8, 0xa8, 0x01, 0x79, 0xea, - 0xb3, 0x78, 0xb1, 0xc0, 0x87, 0xfb, 0xee, 0xb5, 0xc3, 0x15, 0x8d, 0x0e, 0x93, 0xc1, 0x42, 0x54, - 0xfd, 0x3a, 0x2c, 0x5e, 0x98, 0x04, 0x0b, 0x93, 0xba, 0xf5, 0xdd, 0x9e, 0xae, 0xcc, 0xa1, 0x0a, - 0x14, 0xb5, 0x4e, 0xbb, 0xdf, 0x6a, 0xef, 0xb2, 0x38, 0xaf, 0x02, 0x45, 0xdc, 0xd9, 0xde, 0x6e, - 0xd4, 0xb5, 0x67, 0x4a, 0x56, 0xad, 0x41, 0x39, 0xa1, 0x0d, 0x2d, 0x01, 0xf4, 0xfa, 0x9d, 0xae, - 0xb1, 0xd9, 0xc2, 0xbd, 0xbe, 0x88, 0x12, 0x7b, 0xfd, 0x3a, 0xee, 0x4b, 0x42, 0x46, 0xfd, 0x8f, - 0x6c, 0xb4, 0xa2, 0x32, 0x30, 0x6c, 0x5c, 0x0c, 0x0c, 0xaf, 0x18, 0xbc, 0x0c, 0x0d, 0xa7, 0x8d, - 0x38, 0x40, 0x7c, 0x0f, 0x80, 0x6f, 0x1c, 0x62, 0x1b, 0x66, 0x28, 0x17, 0xbe, 0xfa, 0x8a, 0x91, - 0xfb, 0x51, 0x45, 0x0b, 0x97, 0x24, 0xba, 0x1e, 0xa2, 0x6f, 0x40, 0xc5, 0xa2, 0xc3, 0x91, 0x4b, - 0xa4, 0xf0, 0xfc, 0xb5, 0xc2, 0xe5, 0x18, 0x5f, 0x0f, 0x93, 0xa1, 0x69, 0xee, 0x62, 0xf0, 0xfc, - 0x6b, 0x99, 0xc8, 0x32, 0x29, 0xd1, 0x68, 0x05, 0x8a, 0xbb, 0xdd, 0x66, 0xbd, 0xdf, 0x6a, 0x3f, - 0x55, 0x32, 0x08, 0xa0, 0xc0, 0x4d, 0xdd, 0x54, 0xb2, 0x2c, 0x8a, 0xd6, 0x3a, 0x3b, 0xdd, 0x6d, - 0x9d, 0x7b, 0x2c, 0xb4, 0x02, 0x4a, 0x64, 0x6c, 0x83, 0x1b, 0x52, 0x6f, 0x2a, 0x39, 0x74, 0x13, - 0x96, 0x63, 0xaa, 0x94, 0xcc, 0xa3, 0x5b, 0x80, 0x62, 0xe2, 0x54, 0x45, 0x41, 0xfd, 0x65, 0x58, - 0xd6, 0xa8, 0x17, 0x9a, 0x8e, 0x17, 0x67, 0x18, 0x8f, 0xd8, 0xa4, 0x25, 0xc9, 0x70, 0x64, 0x25, - 0xa8, 0xb1, 0x7c, 0x7e, 0xb6, 0x5e, 0x8e, 0xa1, 0xad, 0x26, 0x0f, 0x95, 0x64, 0xc3, 0x66, 0xe7, - 0x77, 0xe4, 0xd8, 0xdc, 0xb8, 0xf9, 0xc6, 0xc2, 0xf9, 0xd9, 0xfa, 0x7c, 0xb7, 0xd5, 0xc4, 0x8c, - 0x86, 0xbe, 0x08, 0x25, 0xf2, 0xd2, 0x09, 0x0d, 0x8b, 0xdd, 0x4b, 0xcc, 0x80, 0x79, 0x5c, 0x64, - 0x04, 0x8d, 0x5d, 0x43, 0x0d, 0x80, 0x2e, 0xf5, 0x43, 0xd9, 0xf3, 0x57, 0x21, 0x3f, 0xa2, 0x3e, - 0xaf, 0x5d, 0x5c, 0x5a, 0x51, 0x63, 0x70, 0xb1, 0x51, 0xb1, 0x00, 0xab, 0xbf, 0x3b, 0x0f, 0xd0, - 0x37, 0x83, 0x43, 0xa9, 0xe4, 0x09, 0x94, 0xe2, 0xea, 0xa4, 0x2c, 0x82, 0x5c, 0xb9, 0xda, 0x31, - 0x18, 0x3d, 0x8e, 0x36, 0x9b, 0xc8, 0x9d, 0x52, 0x93, 0xd8, 0xa8, 0xa3, 0xb4, 0xf4, 0xe3, 0x62, - 0x82, 0xc4, 0xae, 0x79, 0xe2, 0xfb, 0x72, 0xe5, 0xd9, 0x4f, 0xa4, 0xf1, 0x6b, 0x41, 0x18, 0x4d, - 0x46, 0xdf, 0xa9, 0x65, 0x9f, 0x99, 0x15, 0xd9, 0x9a, 0xc3, 0x53, 0x39, 0xf4, 0x21, 0x94, 0xd9, - 0xbc, 0x8d, 0x80, 0xf3, 0x64, 0xe0, 0x7d, 0xa9, 0xa9, 0x84, 0x06, 0x0c, 0xa3, 0xa9, 0x95, 0x5f, - 0x07, 0x30, 0x47, 0x23, 0xd7, 0x21, 0xb6, 0xb1, 0x37, 0xe1, 0x91, 0x76, 0x09, 0x97, 0x24, 0xa5, - 0x31, 0x61, 0xc7, 0x25, 0x62, 0x9b, 0x21, 0x8f, 0x9e, 0xaf, 0x31, 0xa0, 0x44, 0xd7, 0xc3, 0x86, - 0x02, 0x4b, 0xfe, 0xd8, 0x63, 0x06, 0x95, 0xa3, 0x53, 0xff, 0x34, 0x0b, 0xaf, 0xb5, 0x49, 0x78, - 0x4c, 0xfd, 0xc3, 0x7a, 0x18, 0x9a, 0xd6, 0xc1, 0x90, 0x78, 0x72, 0xf9, 0x12, 0x09, 0x4d, 0xe6, - 0x42, 0x42, 0xb3, 0x0a, 0x0b, 0xa6, 0xeb, 0x98, 0x01, 0x11, 0xc1, 0x5b, 0x09, 0x47, 0x4d, 0x96, - 0x76, 0xb1, 0x24, 0x8e, 0x04, 0x01, 0x11, 0x75, 0x15, 0x36, 0xf0, 0x88, 0x80, 0xbe, 0x0b, 0xb7, - 0x64, 0x98, 0x66, 0xc6, 0x5d, 0xb1, 0x84, 0x22, 0x2a, 0xd0, 0xea, 0xa9, 0x59, 0x65, 0xfa, 0xe0, - 0x64, 0x1c, 0x37, 0x25, 0x77, 0x46, 0xa1, 0x8c, 0x0a, 0x57, 0xec, 0x14, 0x56, 0xf5, 0x29, 0xdc, - 0xbe, 0x54, 0xe4, 0x73, 0xd5, 0x6d, 0xfe, 0x31, 0x0b, 0xd0, 0xea, 0xd6, 0x77, 0xa4, 0x91, 0x9a, - 0x50, 0xd8, 0x37, 0x87, 0x8e, 0x3b, 0xb9, 0xca, 0x03, 0x4e, 0xf1, 0xb5, 0xba, 0x30, 0xc7, 0x26, - 0x97, 0xc1, 0x52, 0x96, 0xe7, 0x94, 0xe3, 0x3d, 0x8f, 0x84, 0x71, 0x4e, 0xc9, 0x5b, 0x6c, 0x18, - 0xbe, 0xe9, 0xc5, 0x5b, 0x57, 0x34, 0xd8, 0x02, 0xb0, 0x90, 0xe7, 0xd8, 0x9c, 0x44, 0x6e, 0x4b, - 0x36, 0xd1, 0x16, 0xaf, 0x8e, 0x12, 0xff, 0x88, 0xd8, 0xab, 0x79, 0x6e, 0xd4, 0xeb, 0xc6, 0x83, - 0x25, 0x5c, 0xd8, 0x2e, 0x96, 0xae, 0x7e, 0xc0, 0x43, 0xa6, 0x29, 0xeb, 0x73, 0xd9, 0xe8, 0x01, - 0x2c, 0x5e, 0x98, 0xe7, 0x2b, 0xc9, 0x7c, 0xab, 0xfb, 0xfc, 0xab, 0x4a, 0x4e, 0xfe, 0xfa, 0xba, - 0x52, 0x50, 0xff, 0x76, 0x5e, 0x38, 0x1a, 0x69, 0xd5, 0xf4, 0x57, 0x81, 0x22, 0xdf, 0xdd, 0x16, - 0x75, 0xa5, 0x03, 0x78, 0xfb, 0x6a, 0xff, 0xc3, 0x72, 0x3a, 0x0e, 0xc7, 0xb1, 0x20, 0x5a, 0x87, - 0xb2, 0xd8, 0xc5, 0x06, 0x3b, 0x70, 0xdc, 0xac, 0x8b, 0x18, 0x04, 0x89, 0x49, 0xa2, 0xbb, 0xb0, - 0xc4, 0x8b, 0x3f, 0xc1, 0x01, 0xb1, 0x05, 0x26, 0xc7, 0x31, 0x8b, 0x31, 0x95, 0xc3, 0x76, 0xa0, - 0x22, 0x09, 0x06, 0x8f, 0xe7, 0xf3, 0x7c, 0x40, 0xf7, 0xae, 0x1b, 0x90, 0x10, 0xe1, 0x61, 0x7e, - 0x79, 0x34, 0x6d, 0xa8, 0x3f, 0x0f, 0xc5, 0x68, 0xb0, 0x68, 0x15, 0xe6, 0xfb, 0x5a, 0x57, 0x99, - 0xab, 0x2e, 0x9f, 0x9c, 0x6e, 0x94, 0x23, 0x72, 0x5f, 0xeb, 0x32, 0xce, 0x6e, 0xb3, 0xab, 0x64, - 0x2e, 0x72, 0x76, 0x9b, 0x5d, 0x54, 0x85, 0x5c, 0x4f, 0xeb, 0x77, 0xa3, 0xf8, 0x2c, 0x62, 0x31, - 0x5a, 0x35, 0xc7, 0xe2, 0x33, 0x75, 0x1f, 0xca, 0x89, 0xde, 0xd1, 0x9b, 0xb0, 0xd0, 0x6a, 0x3f, - 0xc5, 0x7a, 0xaf, 0xa7, 0xcc, 0x55, 0x6f, 0x9d, 0x9c, 0x6e, 0xa0, 0x04, 0xb7, 0xe5, 0x0d, 0xd8, - 0xda, 0xa1, 0xd7, 0x21, 0xb7, 0xd5, 0x61, 0xf7, 0xbe, 0x48, 0x2e, 0x12, 0x88, 0x2d, 0x1a, 0x84, - 0xd5, 0x9b, 0x32, 0xf0, 0x4b, 0x2a, 0x56, 0x7f, 0x2f, 0x03, 0x05, 0x71, 0xd0, 0x52, 0x17, 0xb1, - 0x0e, 0x0b, 0x51, 0x09, 0x41, 0x24, 0x7e, 0x6f, 0x5f, 0x9e, 0xa4, 0xd5, 0x64, 0x4e, 0x25, 0xb6, - 0x66, 0x24, 0x57, 0x7d, 0x1f, 0x2a, 0x49, 0xc6, 0xe7, 0xda, 0x98, 0xdf, 0x85, 0x32, 0xdb, 0xfb, - 0x51, 0xb2, 0xf6, 0x08, 0x0a, 0xc2, 0x59, 0xc4, 0xf7, 0xd0, 0xe5, 0x19, 0xa3, 0x44, 0xa2, 0x27, - 0xb0, 0x20, 0xb2, 0xcc, 0xa8, 0x72, 0xbc, 0x76, 0xf5, 0x09, 0xc3, 0x11, 0x5c, 0xfd, 0x10, 0x72, - 0x5d, 0x42, 0x7c, 0x66, 0x7b, 0x8f, 0xda, 0x64, 0x7a, 0x75, 0xcb, 0x04, 0xd9, 0x26, 0xad, 0x26, - 0x4b, 0x90, 0x6d, 0xd2, 0xb2, 0xe3, 0xda, 0x58, 0x36, 0x51, 0x1b, 0xeb, 0x43, 0xe5, 0x05, 0x71, - 0x06, 0x07, 0x21, 0xb1, 0xb9, 0xa2, 0x77, 0x21, 0x37, 0x22, 0xf1, 0xe0, 0x57, 0x53, 0x37, 0x1f, - 0x21, 0x3e, 0xe6, 0x28, 0xe6, 0x63, 0x8e, 0xb9, 0xb4, 0x7c, 0xee, 0x90, 0x2d, 0xf5, 0x1f, 0xb2, - 0xb0, 0xd4, 0x0a, 0x82, 0xb1, 0xe9, 0x59, 0x51, 0x54, 0xf7, 0xcd, 0x8b, 0x51, 0x5d, 0xea, 0xbb, - 0xd0, 0x45, 0x91, 0x8b, 0x25, 0x3f, 0x79, 0xb3, 0x66, 0xe3, 0x9b, 0x55, 0xfd, 0x71, 0x26, 0xaa, - 0xeb, 0xdd, 0x4d, 0xb8, 0x02, 0x91, 0x23, 0x26, 0x35, 0x91, 0x5d, 0xef, 0xd0, 0xa3, 0xc7, 0x1e, - 0x7a, 0x03, 0xf2, 0x58, 0x6f, 0xeb, 0x2f, 0x94, 0x8c, 0xd8, 0x9e, 0x17, 0x40, 0x98, 0x78, 0xe4, - 0x98, 0x69, 0xea, 0xea, 0xed, 0x26, 0x8b, 0xc2, 0xb2, 0x29, 0x9a, 0xba, 0xc4, 0xb3, 0x1d, 0x6f, - 0x80, 0xde, 0x84, 0x42, 0xab, 0xd7, 0xdb, 0xe5, 0x29, 0xe4, 0x6b, 0x27, 0xa7, 0x1b, 0x37, 0x2f, - 0xa0, 0x78, 0x4d, 0xd7, 0x66, 0x20, 0x96, 0x02, 0xb1, 0xf8, 0x2c, 0x05, 0xc4, 0x62, 0x6b, 0x01, - 0xc2, 0x9d, 0x7e, 0xbd, 0xaf, 0x2b, 0xf9, 0x14, 0x10, 0xa6, 0xec, 0xaf, 0x3c, 0x6e, 0xff, 0x92, - 0x05, 0xa5, 0x6e, 0x59, 0x64, 0x14, 0x32, 0xbe, 0xcc, 0x3a, 0xfb, 0x50, 0x1c, 0xb1, 0x5f, 0x0e, - 0x89, 0x22, 0xa8, 0x27, 0xa9, 0x2f, 0x9b, 0x33, 0x72, 0x35, 0x4c, 0x5d, 0x52, 0xb7, 0x87, 0x4e, - 0x10, 0x38, 0xd4, 0x13, 0x34, 0x1c, 0x6b, 0xaa, 0xfe, 0x67, 0x06, 0x6e, 0xa6, 0x20, 0xd0, 0x03, - 0xc8, 0xf9, 0xd4, 0x8d, 0xd6, 0xf0, 0xce, 0x65, 0x25, 0x5b, 0x26, 0x8a, 0x39, 0x12, 0xad, 0x01, - 0x98, 0xe3, 0x90, 0x9a, 0xbc, 0x7f, 0xbe, 0x7a, 0x45, 0x9c, 0xa0, 0xa0, 0x17, 0x50, 0x08, 0x88, - 0xe5, 0x93, 0x28, 0xce, 0xfe, 0xf0, 0xff, 0x3a, 0xfa, 0x5a, 0x8f, 0xab, 0xc1, 0x52, 0x5d, 0xb5, - 0x06, 0x05, 0x41, 0x61, 0xdb, 0xde, 0x36, 0x43, 0x53, 0x16, 0xf4, 0xf9, 0x6f, 0xb6, 0x9b, 0x4c, - 0x77, 0x10, 0xed, 0x26, 0xd3, 0x1d, 0xa8, 0x7f, 0x93, 0x05, 0xd0, 0x5f, 0x86, 0xc4, 0xf7, 0x4c, - 0x57, 0xab, 0x23, 0x3d, 0x71, 0x33, 0x88, 0xd9, 0x7e, 0x39, 0xf5, 0x95, 0x22, 0x96, 0xa8, 0x69, - 0xf5, 0x94, 0xbb, 0xe1, 0x36, 0xcc, 0x8f, 0x7d, 0xf9, 0x58, 0x2d, 0x62, 0xe4, 0x5d, 0xbc, 0x8d, - 0x19, 0x0d, 0xe9, 0x53, 0xb7, 0x35, 0x7f, 0xf9, 0x93, 0x74, 0xa2, 0x83, 0x54, 0xd7, 0xc5, 0x4e, - 0xbe, 0x65, 0x1a, 0x16, 0x91, 0xb7, 0x4a, 0x45, 0x9c, 0x7c, 0xad, 0xae, 0x11, 0x3f, 0xc4, 0x05, - 0xcb, 0x64, 0xff, 0x7f, 0x22, 0xff, 0xf6, 0x2e, 0xc0, 0x74, 0x6a, 0x68, 0x0d, 0xf2, 0xda, 0x66, - 0xaf, 0xb7, 0xad, 0xcc, 0x09, 0x07, 0x3e, 0x65, 0x71, 0xb2, 0xfa, 0x97, 0x59, 0x28, 0x6a, 0x75, - 0x79, 0xe5, 0x6a, 0xa0, 0x70, 0xaf, 0xc4, 0x9f, 0x41, 0xc8, 0xcb, 0x91, 0xe3, 0x4f, 0xa4, 0x63, - 0xb9, 0x22, 0xe1, 0x5d, 0x62, 0x22, 0x6c, 0xd4, 0x3a, 0x17, 0x40, 0x18, 0x2a, 0x44, 0x1a, 0xc1, - 0xb0, 0xcc, 0xc8, 0xc7, 0xaf, 0x5d, 0x6d, 0x2c, 0x91, 0xba, 0x4c, 0xdb, 0x01, 0x2e, 0x47, 0x4a, - 0x34, 0x33, 0x40, 0xef, 0xc1, 0x72, 0xe0, 0x0c, 0x3c, 0xc7, 0x1b, 0x18, 0x91, 0xf1, 0xf8, 0x9b, - 0x4c, 0xe3, 0xc6, 0xf9, 0xd9, 0xfa, 0x62, 0x4f, 0xb0, 0xa4, 0x0d, 0x17, 0x25, 0x52, 0xe3, 0xa6, - 0x44, 0x5f, 0x87, 0xa5, 0x84, 0x28, 0xb3, 0xa2, 0x30, 0xbb, 0x72, 0x7e, 0xb6, 0x5e, 0x89, 0x25, - 0x9f, 0x91, 0x09, 0xae, 0xc4, 0x82, 0xcf, 0x08, 0xaf, 0xcd, 0xec, 0x53, 0xdf, 0x22, 0x86, 0xcf, - 0xcf, 0x34, 0xbf, 0xdd, 0x73, 0xb8, 0xcc, 0x69, 0xe2, 0x98, 0xab, 0xcf, 0xe1, 0x66, 0xc7, 0xb7, - 0x0e, 0x48, 0x10, 0x0a, 0x53, 0x48, 0x2b, 0x7e, 0x08, 0x77, 0x42, 0x33, 0x38, 0x34, 0x0e, 0x9c, - 0x20, 0xa4, 0xfe, 0xc4, 0xf0, 0x49, 0x48, 0x3c, 0xc6, 0x37, 0xf8, 0x43, 0xae, 0x2c, 0x08, 0xde, - 0x66, 0x98, 0x2d, 0x01, 0xc1, 0x11, 0x62, 0x9b, 0x01, 0xd4, 0x16, 0x54, 0x58, 0x0a, 0x23, 0x8b, - 0x6a, 0x6c, 0xf6, 0xe0, 0xd2, 0x81, 0xf1, 0x99, 0xaf, 0xa9, 0x92, 0x4b, 0x07, 0xe2, 0xa7, 0xfa, - 0x6d, 0x50, 0x9a, 0x4e, 0x30, 0x32, 0x43, 0xeb, 0x20, 0xaa, 0x74, 0xa2, 0x26, 0x28, 0x07, 0xc4, - 0xf4, 0xc3, 0x3d, 0x62, 0x86, 0xc6, 0x88, 0xf8, 0x0e, 0xb5, 0xaf, 0x5f, 0xe5, 0xe5, 0x58, 0xa4, - 0xcb, 0x25, 0xd4, 0xff, 0xce, 0x00, 0x60, 0x73, 0x3f, 0x8a, 0xd6, 0xbe, 0x02, 0x37, 0x02, 0xcf, - 0x1c, 0x05, 0x07, 0x34, 0x34, 0x1c, 0x2f, 0x24, 0xfe, 0x91, 0xe9, 0xca, 0xe2, 0x8e, 0x12, 0x31, - 0x5a, 0x92, 0x8e, 0xde, 0x05, 0x74, 0x48, 0xc8, 0xc8, 0xa0, 0xae, 0x6d, 0x44, 0x4c, 0xf1, 0xcc, - 0x9c, 0xc3, 0x0a, 0xe3, 0x74, 0x5c, 0xbb, 0x17, 0xd1, 0x51, 0x03, 0xd6, 0xd8, 0xf4, 0x89, 0x17, - 0xfa, 0x0e, 0x09, 0x8c, 0x7d, 0xea, 0x1b, 0x81, 0x4b, 0x8f, 0x8d, 0x7d, 0xea, 0xba, 0xf4, 0x98, - 0xf8, 0x51, 0xdd, 0xac, 0xea, 0xd2, 0x81, 0x2e, 0x40, 0x9b, 0xd4, 0xef, 0xb9, 0xf4, 0x78, 0x33, - 0x42, 0xb0, 0x90, 0x6e, 0x3a, 0xe7, 0xd0, 0xb1, 0x0e, 0xa3, 0x90, 0x2e, 0xa6, 0xf6, 0x1d, 0xeb, - 0x10, 0xbd, 0x09, 0x8b, 0xc4, 0x25, 0xbc, 0x7c, 0x22, 0x50, 0x79, 0x8e, 0xaa, 0x44, 0x44, 0x06, - 0x52, 0x3f, 0x02, 0x45, 0xf7, 0x2c, 0x7f, 0x32, 0x4a, 0xac, 0xf9, 0xbb, 0x80, 0x98, 0x93, 0x34, - 0x5c, 0x6a, 0x1d, 0x1a, 0x43, 0xd3, 0x33, 0x07, 0x6c, 0x5c, 0xe2, 0xf5, 0x4f, 0x61, 0x9c, 0x6d, - 0x6a, 0x1d, 0xee, 0x48, 0xba, 0xfa, 0x1e, 0x40, 0x6f, 0xe4, 0x13, 0xd3, 0xee, 0xb0, 0x68, 0x82, - 0x99, 0x8e, 0xb7, 0x0c, 0x5b, 0xbe, 0x9e, 0x52, 0x5f, 0x1e, 0x75, 0x45, 0x30, 0x9a, 0x31, 0x5d, - 0xfd, 0x59, 0xb8, 0xd9, 0x75, 0x4d, 0x8b, 0x7f, 0x49, 0xd0, 0x8d, 0x9f, 0xb3, 0xd0, 0x13, 0x28, - 0x08, 0xa8, 0x5c, 0xc9, 0xd4, 0xe3, 0x36, 0xed, 0x73, 0x6b, 0x0e, 0x4b, 0x7c, 0xa3, 0x02, 0x30, - 0xd5, 0xa3, 0xfe, 0x79, 0x06, 0x4a, 0xb1, 0x7e, 0xb4, 0x21, 0x5e, 0x69, 0x42, 0xdf, 0x74, 0x3c, - 0x99, 0xf1, 0x97, 0x70, 0x92, 0x84, 0x5a, 0x50, 0x1e, 0xc5, 0xd2, 0x57, 0xc6, 0x73, 0x29, 0xa3, - 0xc6, 0x49, 0x59, 0xf4, 0x3e, 0x94, 0xa2, 0xe7, 0xea, 0xc8, 0xc3, 0x5e, 0xfd, 0xba, 0x3d, 0x85, - 0xab, 0xdf, 0x04, 0xf8, 0x16, 0x75, 0xbc, 0x3e, 0x3d, 0x24, 0x1e, 0x7f, 0x7e, 0x65, 0xf9, 0x22, - 0x89, 0xac, 0x28, 0x5b, 0xbc, 0x0c, 0x20, 0x96, 0x20, 0x7e, 0x85, 0x14, 0x4d, 0xf5, 0xaf, 0xb3, - 0x50, 0xc0, 0x94, 0x86, 0x5a, 0x1d, 0x6d, 0x40, 0x41, 0xfa, 0x09, 0x7e, 0xff, 0x34, 0x4a, 0xe7, - 0x67, 0xeb, 0x79, 0xe1, 0x20, 0xf2, 0x16, 0xf7, 0x0c, 0x09, 0x0f, 0x9e, 0xbd, 0xcc, 0x83, 0xa3, - 0x07, 0x50, 0x91, 0x20, 0xe3, 0xc0, 0x0c, 0x0e, 0x44, 0xf2, 0xd6, 0x58, 0x3a, 0x3f, 0x5b, 0x07, - 0x81, 0xdc, 0x32, 0x83, 0x03, 0x0c, 0x02, 0xcd, 0x7e, 0x23, 0x1d, 0xca, 0x1f, 0x53, 0xc7, 0x33, - 0x42, 0x3e, 0x09, 0x59, 0x68, 0x4c, 0x5d, 0xc7, 0xe9, 0x54, 0xe5, 0xb7, 0x08, 0xf0, 0xf1, 0x74, - 0xf2, 0x3a, 0x2c, 0xfa, 0x94, 0x86, 0xc2, 0x6d, 0x39, 0xd4, 0x93, 0x35, 0x8c, 0x8d, 0xd4, 0xd2, - 0x36, 0xa5, 0x21, 0x96, 0x38, 0x5c, 0xf1, 0x13, 0x2d, 0xf4, 0x00, 0x56, 0x5c, 0x33, 0x08, 0x0d, - 0xee, 0xef, 0xec, 0xa9, 0xb6, 0x02, 0x3f, 0x6a, 0x88, 0xf1, 0x36, 0x39, 0x2b, 0x92, 0x50, 0xff, - 0x29, 0x03, 0x65, 0x36, 0x19, 0x67, 0xdf, 0xb1, 0x58, 0x90, 0xf7, 0xf9, 0x63, 0x8f, 0xdb, 0x30, - 0x6f, 0x05, 0xbe, 0x34, 0x2a, 0xbf, 0x7c, 0xb5, 0x1e, 0xc6, 0x8c, 0x86, 0x3e, 0x82, 0x82, 0xac, - 0xa5, 0x88, 0xb0, 0x43, 0xbd, 0x3e, 0x1c, 0x95, 0xb6, 0x91, 0x72, 0x7c, 0x2f, 0x4f, 0x47, 0x27, - 0x2e, 0x01, 0x9c, 0x24, 0xa1, 0x5b, 0x90, 0xb5, 0x84, 0xb9, 0xe4, 0xc7, 0x2e, 0x5a, 0x1b, 0x67, - 0x2d, 0x4f, 0xfd, 0x41, 0x06, 0x16, 0xa7, 0x07, 0x9e, 0xed, 0x80, 0x3b, 0x50, 0x0a, 0xc6, 0x7b, - 0xc1, 0x24, 0x08, 0xc9, 0x30, 0x7a, 0x5a, 0x8e, 0x09, 0xa8, 0x05, 0x25, 0xd3, 0x1d, 0x50, 0xdf, - 0x09, 0x0f, 0x86, 0x32, 0x4b, 0x4d, 0x0f, 0x15, 0x92, 0x3a, 0x6b, 0xf5, 0x48, 0x04, 0x4f, 0xa5, - 0xa3, 0x7b, 0x5f, 0x7c, 0x7f, 0xc0, 0xef, 0xfd, 0x37, 0xa0, 0xe2, 0x9a, 0x43, 0x5e, 0x5c, 0x0a, - 0x9d, 0xa1, 0x98, 0x47, 0x0e, 0x97, 0x25, 0xad, 0xef, 0x0c, 0x89, 0xaa, 0x42, 0x29, 0x56, 0x86, - 0x96, 0xa1, 0x5c, 0xd7, 0x7b, 0xc6, 0xc3, 0x47, 0x4f, 0x8c, 0xa7, 0xda, 0x8e, 0x32, 0x27, 0x63, - 0xd3, 0xbf, 0xc8, 0xc0, 0xa2, 0x74, 0x47, 0x32, 0xde, 0x7f, 0x13, 0x16, 0x7c, 0x73, 0x3f, 0x8c, - 0x32, 0x92, 0x9c, 0xd8, 0xd5, 0xcc, 0xc3, 0xb3, 0x8c, 0x84, 0xb1, 0xd2, 0x33, 0x92, 0xc4, 0xc7, - 0x0e, 0xf3, 0x57, 0x7e, 0xec, 0x90, 0xfb, 0xa9, 0x7c, 0xec, 0xa0, 0xfe, 0x0a, 0xc0, 0xa6, 0xe3, - 0x92, 0xbe, 0xa8, 0x43, 0xa5, 0xe5, 0x97, 0x2c, 0x86, 0x93, 0x75, 0xce, 0x28, 0x86, 0x6b, 0x35, - 0x31, 0xa3, 0x31, 0xd6, 0xc0, 0xb1, 0xe5, 0x61, 0xe4, 0xac, 0xa7, 0x8c, 0x35, 0x70, 0xec, 0xf8, - 0x55, 0x2e, 0x77, 0xdd, 0xab, 0xdc, 0x69, 0x06, 0x96, 0x65, 0xec, 0x1a, 0xbb, 0xdf, 0x2f, 0x43, - 0x49, 0x84, 0xb1, 0xd3, 0x84, 0x8e, 0x3f, 0xf0, 0x0b, 0x5c, 0xab, 0x89, 0x8b, 0x82, 0xdd, 0xb2, - 0xd1, 0x3a, 0x94, 0x25, 0x34, 0xf1, 0x61, 0x14, 0x08, 0x52, 0x9b, 0x0d, 0xff, 0xab, 0x90, 0xdb, - 0x77, 0x5c, 0x22, 0x37, 0x7a, 0xaa, 0x03, 0x98, 0x1a, 0x60, 0x6b, 0x0e, 0x73, 0x74, 0xa3, 0x18, - 0x15, 0xea, 0xf8, 0xf8, 0x64, 0xda, 0x99, 0x1c, 0x9f, 0xc8, 0x40, 0x67, 0xc6, 0x27, 0x70, 0x6c, - 0x7c, 0x82, 0x2d, 0xc6, 0x27, 0xa1, 0xc9, 0xf1, 0x09, 0xd2, 0x4f, 0x65, 0x7c, 0xdb, 0x70, 0xab, - 0xe1, 0x9a, 0xd6, 0xa1, 0xeb, 0x04, 0x21, 0xb1, 0x93, 0x1e, 0xe3, 0x11, 0x14, 0x2e, 0x04, 0x9d, - 0x57, 0x55, 0x34, 0x25, 0x52, 0xfd, 0xf7, 0x0c, 0x54, 0xb6, 0x88, 0xe9, 0x86, 0x07, 0xd3, 0xb2, - 0x51, 0x48, 0x82, 0x50, 0x5e, 0x56, 0xfc, 0x37, 0xfa, 0x1a, 0x14, 0xe3, 0x98, 0xe4, 0xda, 0xb7, - 0xb9, 0x18, 0x8a, 0x1e, 0xc3, 0x02, 0x3b, 0x63, 0x74, 0x1c, 0x25, 0x3b, 0x57, 0x3d, 0xfb, 0x48, - 0x24, 0xbb, 0x64, 0x7c, 0xc2, 0x83, 0x10, 0xbe, 0x95, 0xf2, 0x38, 0x6a, 0xa2, 0xff, 0x0f, 0x15, - 0xfe, 0x6a, 0x11, 0xc5, 0x5c, 0xf9, 0xeb, 0x74, 0x96, 0xc5, 0xc3, 0xa3, 0x88, 0xb7, 0xfe, 0x38, - 0x0b, 0x2b, 0x3b, 0xe6, 0x64, 0x8f, 0x48, 0xb7, 0x41, 0x6c, 0x4c, 0x2c, 0xea, 0xdb, 0xa8, 0x9b, - 0x74, 0x37, 0x57, 0xbc, 0x63, 0xa6, 0x09, 0xa7, 0x7b, 0x9d, 0x28, 0x01, 0xcb, 0x26, 0x12, 0xb0, - 0x15, 0xc8, 0x7b, 0xd4, 0xb3, 0x88, 0xf4, 0x45, 0xa2, 0xa1, 0xfe, 0x76, 0x26, 0xe9, 0x6b, 0xaa, - 0xf1, 0x1b, 0x23, 0xaf, 0x40, 0xb5, 0x69, 0x18, 0x77, 0x87, 0x3e, 0x82, 0x6a, 0x4f, 0xd7, 0xb0, - 0xde, 0x6f, 0x74, 0xbe, 0x6d, 0xf4, 0xea, 0xdb, 0xbd, 0xfa, 0xa3, 0x07, 0x46, 0xb7, 0xb3, 0xfd, - 0x9d, 0x87, 0x8f, 0x1f, 0x7c, 0x4d, 0xc9, 0x54, 0x37, 0x4e, 0x4e, 0x37, 0xee, 0xb4, 0xeb, 0xda, - 0xb6, 0x38, 0x32, 0x7b, 0xf4, 0x65, 0xcf, 0x74, 0x03, 0xf3, 0xd1, 0x83, 0x2e, 0x75, 0x27, 0x0c, - 0x83, 0xbe, 0x02, 0x68, 0x53, 0xc7, 0x6d, 0xbd, 0x6f, 0x44, 0x0e, 0x4d, 0x6b, 0x68, 0x4a, 0x56, - 0xa4, 0x35, 0x9b, 0xc4, 0xf7, 0x48, 0x58, 0xd7, 0x7b, 0x0f, 0x1f, 0x3d, 0xd1, 0x1a, 0x1a, 0x3b, - 0x04, 0x95, 0xe4, 0xed, 0x96, 0xbc, 0xb4, 0x33, 0x97, 0x5e, 0xda, 0xd3, 0xbb, 0x3f, 0x7b, 0xc9, - 0xdd, 0xbf, 0x09, 0x2b, 0x96, 0x4f, 0x83, 0xc0, 0x60, 0xb9, 0x02, 0xb1, 0x67, 0xb2, 0x91, 0x2f, - 0x9c, 0x9f, 0xad, 0xdf, 0xd0, 0x18, 0xbf, 0xc7, 0xd9, 0x52, 0xfd, 0x0d, 0x2b, 0x41, 0xe2, 0x3d, - 0xa9, 0xbf, 0x3f, 0xcf, 0xc2, 0x2e, 0xe7, 0xc8, 0x71, 0xc9, 0x80, 0x04, 0xe8, 0x39, 0x2c, 0x5b, - 0x3e, 0xb1, 0x59, 0x12, 0x60, 0xba, 0xc9, 0xcf, 0x71, 0xff, 0x5f, 0x6a, 0x04, 0x14, 0x0b, 0xd6, - 0xb4, 0x58, 0xaa, 0x37, 0x22, 0x16, 0x5e, 0xb2, 0x2e, 0xb4, 0xd1, 0xc7, 0xb0, 0x1c, 0x10, 0xd7, - 0xf1, 0xc6, 0x2f, 0x0d, 0x8b, 0x7a, 0x21, 0x79, 0x19, 0xbd, 0xad, 0x5d, 0xa7, 0xb7, 0xa7, 0x6f, - 0x33, 0x29, 0x4d, 0x08, 0x35, 0xd0, 0xf9, 0xd9, 0xfa, 0xd2, 0x45, 0x1a, 0x5e, 0x92, 0x9a, 0x65, - 0xbb, 0xda, 0x86, 0xa5, 0x8b, 0xa3, 0x41, 0x2b, 0xd2, 0x53, 0x70, 0x87, 0x13, 0x79, 0x02, 0x74, - 0x07, 0x8a, 0x3e, 0x19, 0x38, 0x41, 0xe8, 0x0b, 0x33, 0x33, 0x4e, 0x4c, 0x61, 0x7e, 0x42, 0x7c, - 0x4b, 0x55, 0xfd, 0x25, 0x98, 0xe9, 0x91, 0x1d, 0x2d, 0xdb, 0x09, 0xcc, 0x3d, 0xa9, 0xb2, 0x88, - 0xa3, 0x26, 0xdb, 0xb1, 0xe3, 0x20, 0x0e, 0xeb, 0xf8, 0x6f, 0x46, 0xe3, 0xf1, 0x87, 0xfc, 0xb2, - 0x8c, 0x47, 0x18, 0xd1, 0x27, 0xaa, 0xb9, 0xc4, 0x27, 0xaa, 0x2b, 0x90, 0x77, 0xc9, 0x11, 0x71, - 0xc5, 0xcd, 0x8f, 0x45, 0xe3, 0xde, 0x03, 0xa8, 0x44, 0xdf, 0x42, 0xf2, 0x6f, 0x30, 0x8a, 0x90, - 0xeb, 0xd7, 0x7b, 0xcf, 0x94, 0x39, 0x04, 0x50, 0x10, 0x3b, 0x59, 0xbc, 0xfb, 0x69, 0x9d, 0xf6, - 0x66, 0xeb, 0xa9, 0x92, 0xbd, 0xf7, 0x3b, 0x39, 0x28, 0xc5, 0x2f, 0x4f, 0xec, 0xa6, 0x69, 0xeb, - 0x2f, 0xa2, 0xa3, 0x10, 0xd3, 0xdb, 0xe4, 0x18, 0xbd, 0x31, 0xad, 0x59, 0x7d, 0x24, 0x9e, 0xda, - 0x63, 0x76, 0x54, 0xaf, 0x7a, 0x0b, 0x8a, 0xf5, 0x5e, 0xaf, 0xf5, 0xb4, 0xad, 0x37, 0x95, 0x4f, - 0x33, 0xd5, 0x2f, 0x9c, 0x9c, 0x6e, 0xdc, 0x88, 0x41, 0xf5, 0x40, 0x6c, 0x3e, 0x8e, 0xd2, 0x34, - 0xbd, 0xdb, 0xd7, 0x9b, 0xca, 0x27, 0xd9, 0x59, 0x14, 0xaf, 0xc1, 0xf0, 0x8f, 0x80, 0x4a, 0x5d, - 0xac, 0x77, 0xeb, 0x98, 0x75, 0xf8, 0x69, 0x56, 0x94, 0xd2, 0xa6, 0x3d, 0xfa, 0x64, 0x64, 0xfa, - 0xac, 0xcf, 0xb5, 0xe8, 0xab, 0xba, 0x4f, 0xe6, 0xc5, 0x87, 0x22, 0xd3, 0x67, 0x34, 0x62, 0xda, - 0x13, 0xd6, 0x1b, 0x7f, 0xbf, 0xe4, 0x6a, 0xe6, 0x67, 0x7a, 0xeb, 0x31, 0x4f, 0xc5, 0xb4, 0xa8, - 0xb0, 0x80, 0x77, 0xdb, 0x6d, 0x06, 0xfa, 0x24, 0x37, 0x33, 0x3b, 0x3c, 0xf6, 0x58, 0x7e, 0x8d, - 0xee, 0x42, 0x31, 0x7a, 0xde, 0x54, 0x3e, 0xcd, 0xcd, 0x0c, 0x48, 0x8b, 0xde, 0x66, 0x79, 0x87, - 0x5b, 0xbb, 0x7d, 0xfe, 0xd1, 0xdf, 0x27, 0xf9, 0xd9, 0x0e, 0x0f, 0xc6, 0xa1, 0x4d, 0x8f, 0x3d, - 0x76, 0x66, 0x65, 0xd5, 0xee, 0xd3, 0xbc, 0xf0, 0x05, 0x31, 0x46, 0x96, 0xec, 0xde, 0x82, 0x22, - 0xd6, 0xbf, 0x25, 0xbe, 0x0f, 0xfc, 0xa4, 0x30, 0xa3, 0x07, 0x93, 0x8f, 0x89, 0xc5, 0x7a, 0xdb, - 0x80, 0x02, 0xd6, 0x77, 0x3a, 0xcf, 0x75, 0xe5, 0x0f, 0x0a, 0x33, 0x7a, 0x30, 0x19, 0x52, 0xfe, - 0x95, 0x54, 0xb1, 0x83, 0xbb, 0x5b, 0x75, 0xbe, 0x28, 0xb3, 0x7a, 0x3a, 0xfe, 0xe8, 0xc0, 0xf4, - 0x88, 0x3d, 0xfd, 0x9e, 0x26, 0x66, 0xdd, 0xfb, 0x39, 0x28, 0x46, 0x91, 0x2e, 0x5a, 0x83, 0xc2, - 0x8b, 0x0e, 0x7e, 0xa6, 0x63, 0x65, 0x4e, 0x58, 0x39, 0xe2, 0xbc, 0x10, 0x39, 0xca, 0x06, 0x2c, - 0xec, 0xd4, 0xdb, 0xf5, 0xa7, 0x3a, 0x8e, 0x4a, 0xee, 0x11, 0x40, 0x86, 0x6b, 0x55, 0x45, 0x76, - 0x10, 0xeb, 0x6c, 0xac, 0x7e, 0xff, 0x47, 0x6b, 0x73, 0x3f, 0xfc, 0xd1, 0xda, 0xdc, 0x27, 0xe7, - 0x6b, 0x99, 0xef, 0x9f, 0xaf, 0x65, 0xfe, 0xfe, 0x7c, 0x2d, 0xf3, 0x6f, 0xe7, 0x6b, 0x99, 0xbd, - 0x02, 0xbf, 0x54, 0x1e, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x92, 0xe0, 0x5d, 0x4a, 0x5c, - 0x32, 0x00, 0x00, + 0xcb, 0x95, 0x8e, 0xb7, 0xd3, 0xb6, 0x75, 0xed, 0x74, 0xef, 0x22, 0x41, 0x51, 0xa9, 0xba, 0x71, + 0x6a, 0x52, 0xae, 0x6b, 0xaa, 0xca, 0xe9, 0x36, 0x0b, 0x62, 0xc4, 0x03, 0xa0, 0x3c, 0xc1, 0x0b, + 0x2c, 0x42, 0x41, 0x48, 0xf0, 0xc6, 0x03, 0x0f, 0x20, 0x21, 0x78, 0x1a, 0x24, 0x84, 0x56, 0xbc, + 0xc0, 0x82, 0x84, 0x56, 0x20, 0x05, 0x36, 0x0f, 0xbc, 0xad, 0xe0, 0x05, 0xf1, 0xc2, 0x03, 0xba, + 0x1f, 0x55, 0xae, 0xb8, 0x2b, 0xc9, 0x0c, 0xbb, 0x2f, 0x89, 0xef, 0x39, 0xbf, 0x73, 0xee, 0xbd, + 0xe7, 0xde, 0x7b, 0xee, 0x39, 0xe7, 0x16, 0xdc, 0x19, 0x3a, 0xe1, 0xc1, 0x64, 0xaf, 0x6e, 0xd1, + 0xd1, 0x5d, 0x9b, 0x5a, 0x87, 0xc4, 0xbf, 0x1b, 0xbc, 0x30, 0xfd, 0xd1, 0xa1, 0x13, 0xde, 0x35, + 0xc7, 0xce, 0xdd, 0x70, 0x3a, 0x26, 0x41, 0x7d, 0xec, 0xd3, 0x90, 0x22, 0x24, 0x00, 0xf5, 0x08, + 0x50, 0x3f, 0xba, 0x5f, 0xdd, 0x1c, 0x52, 0x3a, 0x74, 0xc9, 0x5d, 0x8e, 0xd8, 0x9b, 0xec, 0xdf, + 0x0d, 0x9d, 0x11, 0x09, 0x42, 0x73, 0x34, 0x16, 0x42, 0xd5, 0x8d, 0x79, 0x80, 0x3d, 0xf1, 0xcd, + 0xd0, 0xa1, 0x9e, 0xe4, 0xaf, 0x0d, 0xe9, 0x90, 0xf2, 0x9f, 0x77, 0xd9, 0x2f, 0x41, 0x55, 0x37, + 0x61, 0xe9, 0x19, 0xf1, 0x03, 0x87, 0x7a, 0x68, 0x0d, 0xf2, 0x8e, 0x67, 0x93, 0x97, 0xeb, 0x99, + 0x5a, 0xe6, 0x9d, 0x1c, 0x16, 0x0d, 0xf5, 0x1e, 0x40, 0x9b, 0xfd, 0xd0, 0xbd, 0xd0, 0x9f, 0x22, + 0x05, 0x16, 0x0f, 0xc9, 0x94, 0x23, 0x4a, 0x98, 0xfd, 0x64, 0x94, 0x23, 0xd3, 0x5d, 0xcf, 0x0a, + 0xca, 0x91, 0xe9, 0xaa, 0x3f, 0xca, 0x40, 0xb9, 0xe1, 0x79, 0x34, 0xe4, 0xbd, 0x07, 0x08, 0x41, + 0xce, 0x33, 0x47, 0x44, 0x0a, 0xf1, 0xdf, 0x48, 0x83, 0x82, 0x6b, 0xee, 0x11, 0x37, 0x58, 0xcf, + 0xd6, 0x16, 0xdf, 0x29, 0x3f, 0xf8, 0x4a, 0xfd, 0xd5, 0x29, 0xd7, 0x13, 0x4a, 0xea, 0x3b, 0x1c, + 0xcd, 0x07, 0x81, 0xa5, 0x28, 0xfa, 0x26, 0x2c, 0x39, 0x9e, 0xed, 0x58, 0x24, 0x58, 0xcf, 0x71, + 0x2d, 0x1b, 0x69, 0x5a, 0x66, 0xa3, 0x6f, 0xe6, 0xbe, 0x7f, 0xba, 0xb9, 0x80, 0x23, 0xa1, 0xea, + 0x7b, 0x50, 0x4e, 0xa8, 0x4d, 0x99, 0xdb, 0x1a, 0xe4, 0x8f, 0x4c, 0x77, 0x42, 0xe4, 0xec, 0x44, + 0xe3, 0xfd, 0xec, 0xa3, 0x8c, 0xfa, 0x11, 0xac, 0x75, 0xcc, 0x11, 0xb1, 0x1f, 0x13, 0x8f, 0xf8, + 0x8e, 0x85, 0x49, 0x40, 0x27, 0xbe, 0x45, 0xd8, 0x5c, 0x0f, 0x1d, 0xcf, 0x8e, 0xe6, 0xca, 0x7e, + 0xa7, 0x6b, 0x51, 0x35, 0x78, 0xad, 0xe5, 0x04, 0x96, 0x4f, 0x42, 0xf2, 0xb9, 0x95, 0x2c, 0x46, + 0x4a, 0x4e, 0x33, 0xb0, 0x3a, 0x2f, 0xfd, 0x33, 0x70, 0x9d, 0x99, 0xd8, 0x36, 0x7c, 0x49, 0x31, + 0x82, 0x31, 0xb1, 0xb8, 0xb2, 0xf2, 0x83, 0x77, 0xd2, 0x2c, 0x94, 0x36, 0x93, 0xed, 0x05, 0x7c, + 0x8d, 0xab, 0x89, 0x08, 0xfd, 0x31, 0xb1, 0x90, 0x05, 0x37, 0x6c, 0x39, 0xe8, 0x39, 0xf5, 0x59, + 0xae, 0x3e, 0x75, 0x19, 0x2f, 0x98, 0xe6, 0xf6, 0x02, 0x5e, 0x8b, 0x94, 0x25, 0x3b, 0x69, 0x02, + 0x14, 0x23, 0xdd, 0xea, 0xf7, 0x32, 0x50, 0x8a, 0x98, 0x01, 0xfa, 0x32, 0x94, 0x3c, 0xd3, 0xa3, + 0x86, 0x35, 0x9e, 0x04, 0x7c, 0x42, 0x8b, 0xcd, 0xca, 0xd9, 0xe9, 0x66, 0xb1, 0x63, 0x7a, 0x54, + 0xeb, 0xed, 0x06, 0xb8, 0xc8, 0xd8, 0xda, 0x78, 0x12, 0xa0, 0x37, 0xa0, 0x32, 0x22, 0x23, 0xea, + 0x4f, 0x8d, 0xbd, 0x69, 0x48, 0x02, 0x69, 0xb6, 0xb2, 0xa0, 0x35, 0x19, 0x09, 0x7d, 0x03, 0x96, + 0x86, 0x62, 0x48, 0xeb, 0x8b, 0x7c, 0xfb, 0xbc, 0x99, 0x36, 0xfa, 0xb9, 0x51, 0xe3, 0x48, 0x46, + 0xfd, 0xcd, 0x0c, 0xac, 0xc5, 0x54, 0xf2, 0x0b, 0x13, 0xc7, 0x27, 0x23, 0xe2, 0x85, 0x01, 0xfa, + 0x1a, 0x14, 0x5c, 0x67, 0xe4, 0x84, 0x81, 0xb4, 0xf9, 0xeb, 0x69, 0x6a, 0xe3, 0x49, 0x61, 0x09, + 0x46, 0x0d, 0xa8, 0xf8, 0x24, 0x20, 0xfe, 0x91, 0xd8, 0xf1, 0xd2, 0xa2, 0x57, 0x08, 0x9f, 0x13, + 0x51, 0xb7, 0xa0, 0xd8, 0x73, 0xcd, 0x70, 0x9f, 0xfa, 0x23, 0xa4, 0x42, 0xc5, 0xf4, 0xad, 0x03, + 0x27, 0x24, 0x56, 0x38, 0xf1, 0xa3, 0xd3, 0x77, 0x8e, 0x86, 0x6e, 0x40, 0x96, 0x8a, 0x8e, 0x4a, + 0xcd, 0xc2, 0xd9, 0xe9, 0x66, 0xb6, 0xdb, 0xc7, 0x59, 0x1a, 0xa8, 0x1f, 0xc0, 0xb5, 0x9e, 0x3b, + 0x19, 0x3a, 0x5e, 0x8b, 0x04, 0x96, 0xef, 0x8c, 0x99, 0x76, 0xb6, 0x2b, 0x99, 0x8f, 0x8a, 0x76, + 0x25, 0xfb, 0x1d, 0x1f, 0xed, 0xec, 0xec, 0x68, 0xab, 0xbf, 0x9e, 0x85, 0x6b, 0xba, 0x37, 0x74, + 0x3c, 0x92, 0x94, 0xbe, 0x0d, 0x2b, 0x84, 0x13, 0x8d, 0x23, 0xe1, 0x6e, 0xa4, 0x9e, 0x65, 0x41, + 0x8d, 0x7c, 0x50, 0x7b, 0xce, 0x2f, 0xdc, 0x4f, 0x9b, 0xfe, 0x2b, 0xda, 0x53, 0xbd, 0x83, 0x0e, + 0x4b, 0x63, 0x3e, 0x89, 0x40, 0x2e, 0xef, 0xed, 0x34, 0x5d, 0xaf, 0xcc, 0x33, 0x72, 0x12, 0x52, + 0xf6, 0x27, 0x71, 0x12, 0x7f, 0x9b, 0x85, 0xd5, 0x0e, 0xb5, 0xcf, 0xd9, 0xa1, 0x0a, 0xc5, 0x03, + 0x1a, 0x84, 0x09, 0x87, 0x18, 0xb7, 0xd1, 0x23, 0x28, 0x8e, 0xe5, 0xf2, 0xc9, 0xd5, 0xbf, 0x95, + 0x3e, 0x64, 0x81, 0xc1, 0x31, 0x1a, 0x7d, 0x00, 0xa5, 0xe8, 0xc8, 0xb0, 0xd9, 0x7e, 0x86, 0x8d, + 0x33, 0xc3, 0xa3, 0x6f, 0x40, 0x41, 0x2c, 0xc2, 0x7a, 0x8e, 0x4b, 0xde, 0xfe, 0x4c, 0x36, 0xc7, + 0x52, 0x08, 0x3d, 0x86, 0x62, 0xe8, 0x06, 0x86, 0xe3, 0xed, 0xd3, 0xf5, 0x3c, 0x57, 0xb0, 0x99, + 0xea, 0x64, 0xa8, 0x4d, 0x06, 0x3b, 0xfd, 0xb6, 0xb7, 0x4f, 0x9b, 0xe5, 0xb3, 0xd3, 0xcd, 0x25, + 0xd9, 0xc0, 0x4b, 0xa1, 0x1b, 0xb0, 0x1f, 0xe8, 0x16, 0xe4, 0xf6, 0x9d, 0x71, 0xb0, 0x5e, 0xa8, + 0x65, 0xde, 0x29, 0x36, 0x8b, 0x67, 0xa7, 0x9b, 0xb9, 0xad, 0x76, 0xaf, 0x8f, 0x39, 0x55, 0xfd, + 0xad, 0x0c, 0x94, 0x13, 0x3a, 0xd0, 0xeb, 0x00, 0xa1, 0x3f, 0x09, 0x42, 0xc3, 0xa7, 0x34, 0xe4, + 0xa6, 0xac, 0xe0, 0x12, 0xa7, 0x60, 0x4a, 0x43, 0x54, 0x87, 0xeb, 0x16, 0xf1, 0x43, 0xc3, 0x09, + 0x82, 0x09, 0xf1, 0x8d, 0x60, 0xb2, 0xf7, 0x31, 0xb1, 0x42, 0x6e, 0xd6, 0x0a, 0xbe, 0xc6, 0x58, + 0x6d, 0xce, 0xe9, 0x0b, 0x06, 0x7a, 0x08, 0x37, 0x92, 0xf8, 0xf1, 0x64, 0xcf, 0x75, 0x2c, 0x83, + 0x2d, 0xf5, 0x22, 0x17, 0xb9, 0x3e, 0x13, 0xe9, 0x71, 0xde, 0x13, 0x32, 0x55, 0x7f, 0x98, 0x01, + 0x05, 0x9b, 0xfb, 0xe1, 0x53, 0x32, 0xda, 0x23, 0x7e, 0x3f, 0x34, 0xc3, 0x49, 0x80, 0x6e, 0x40, + 0xc1, 0x25, 0xa6, 0x4d, 0x7c, 0x3e, 0xa8, 0x22, 0x96, 0x2d, 0xb4, 0xcb, 0xce, 0xb7, 0x69, 0x1d, + 0x98, 0x7b, 0x8e, 0xeb, 0x84, 0x53, 0x3e, 0x94, 0x95, 0xf4, 0x0d, 0x3e, 0xaf, 0xb3, 0x8e, 0x13, + 0x82, 0xf8, 0x9c, 0x1a, 0xb4, 0x0e, 0x4b, 0x23, 0x12, 0x04, 0xe6, 0x90, 0xf0, 0x91, 0x96, 0x70, + 0xd4, 0x54, 0x3f, 0x80, 0x4a, 0x52, 0x0e, 0x95, 0x61, 0x69, 0xb7, 0xf3, 0xa4, 0xd3, 0x7d, 0xde, + 0x51, 0x16, 0xd0, 0x2a, 0x94, 0x77, 0x3b, 0x58, 0x6f, 0x68, 0xdb, 0x8d, 0xe6, 0x8e, 0xae, 0x64, + 0xd0, 0x32, 0x94, 0x66, 0xcd, 0xac, 0xfa, 0xa7, 0x19, 0x00, 0x66, 0x6e, 0x39, 0xa9, 0xf7, 0x21, + 0x1f, 0x84, 0x66, 0x28, 0xf6, 0xec, 0xca, 0x83, 0xb7, 0x2e, 0x5a, 0x61, 0x39, 0x5e, 0xf6, 0x8f, + 0x60, 0x21, 0x92, 0x1c, 0x61, 0xf6, 0xdc, 0x08, 0x99, 0xfb, 0x30, 0x6d, 0xdb, 0x97, 0x03, 0xe7, + 0xbf, 0xd5, 0x0f, 0x20, 0xcf, 0xa5, 0xcf, 0x0f, 0xb7, 0x08, 0xb9, 0x16, 0xfb, 0x95, 0x41, 0x25, + 0xc8, 0x63, 0xbd, 0xd1, 0xfa, 0x8e, 0x92, 0x45, 0x0a, 0x54, 0x5a, 0xed, 0xbe, 0xd6, 0xed, 0x74, + 0x74, 0x6d, 0xa0, 0xb7, 0x94, 0x45, 0xf5, 0x36, 0xe4, 0xdb, 0x23, 0xa6, 0xf9, 0x16, 0x3b, 0x10, + 0xfb, 0xc4, 0x27, 0x9e, 0x15, 0x9d, 0xb3, 0x19, 0x41, 0xfd, 0x71, 0x19, 0xf2, 0x4f, 0xe9, 0xc4, + 0x0b, 0xd1, 0x83, 0x84, 0x53, 0x5b, 0x49, 0x8f, 0x1f, 0x38, 0xb0, 0x3e, 0x98, 0x8e, 0x89, 0x74, + 0x7a, 0x37, 0xa0, 0x20, 0x8e, 0x8e, 0x9c, 0x8e, 0x6c, 0x31, 0x7a, 0x68, 0xfa, 0x43, 0x12, 0xca, + 0xf9, 0xc8, 0x16, 0x7a, 0x87, 0xdd, 0x67, 0xa6, 0x4d, 0x3d, 0x77, 0xca, 0x4f, 0x58, 0x51, 0x5c, + 0x5a, 0x98, 0x98, 0x76, 0xd7, 0x73, 0xa7, 0x38, 0xe6, 0xa2, 0x6d, 0xa8, 0xec, 0x39, 0x9e, 0x6d, + 0xd0, 0xb1, 0xb8, 0x02, 0xf2, 0x17, 0x9f, 0x47, 0x31, 0xaa, 0xa6, 0xe3, 0xd9, 0x5d, 0x01, 0xc6, + 0xe5, 0xbd, 0x59, 0x03, 0x75, 0x60, 0xe5, 0x88, 0xba, 0x93, 0x11, 0x89, 0x75, 0x15, 0xb8, 0xae, + 0xb7, 0x2f, 0xd6, 0xf5, 0x8c, 0xe3, 0x23, 0x6d, 0xcb, 0x47, 0xc9, 0x26, 0x7a, 0x02, 0xcb, 0xe1, + 0x68, 0xbc, 0x1f, 0xc4, 0xea, 0x96, 0xb8, 0xba, 0x2f, 0x5d, 0x62, 0x30, 0x06, 0x8f, 0xb4, 0x55, + 0xc2, 0x44, 0x0b, 0x3d, 0x86, 0xb2, 0x45, 0xbd, 0xc0, 0x09, 0x42, 0xe2, 0x59, 0xd3, 0xf5, 0x22, + 0xb7, 0xfd, 0x25, 0xb3, 0xd4, 0x66, 0x60, 0x9c, 0x94, 0xac, 0xfe, 0xea, 0x22, 0x94, 0x13, 0x26, + 0x40, 0x7d, 0x28, 0x8f, 0x7d, 0x3a, 0x36, 0x87, 0xfc, 0x3e, 0x94, 0x8b, 0x7a, 0xff, 0x33, 0x99, + 0xaf, 0xde, 0x9b, 0x09, 0xe2, 0xa4, 0x16, 0xf5, 0x24, 0x0b, 0xe5, 0x04, 0x13, 0xdd, 0x81, 0x22, + 0xee, 0xe1, 0xf6, 0xb3, 0xc6, 0x40, 0x57, 0x16, 0xaa, 0xb7, 0x8e, 0x4f, 0x6a, 0xeb, 0x5c, 0x5b, + 0x52, 0x41, 0xcf, 0x77, 0x8e, 0xd8, 0x1e, 0x7e, 0x07, 0x96, 0x22, 0x68, 0xa6, 0xfa, 0xc5, 0xe3, + 0x93, 0xda, 0x6b, 0xf3, 0xd0, 0x04, 0x12, 0xf7, 0xb7, 0x1b, 0x58, 0x6f, 0x29, 0xd9, 0x74, 0x24, + 0xee, 0x1f, 0x98, 0x3e, 0xb1, 0xd1, 0x97, 0xa0, 0x20, 0x81, 0x8b, 0xd5, 0xea, 0xf1, 0x49, 0xed, + 0xc6, 0x3c, 0x70, 0x86, 0xc3, 0xfd, 0x9d, 0xc6, 0x33, 0x5d, 0xc9, 0xa5, 0xe3, 0x70, 0xdf, 0x35, + 0x8f, 0x08, 0x7a, 0x0b, 0xf2, 0x02, 0x96, 0xaf, 0xde, 0x3c, 0x3e, 0xa9, 0x7d, 0xe1, 0x15, 0x75, + 0x0c, 0x55, 0x5d, 0xff, 0x8d, 0x3f, 0xdc, 0x58, 0xf8, 0xcb, 0x3f, 0xda, 0x50, 0xe6, 0xd9, 0xd5, + 0xff, 0xc9, 0xc0, 0xf2, 0xb9, 0xbd, 0x83, 0x54, 0x28, 0x78, 0xd4, 0xa2, 0x63, 0x71, 0x4d, 0x16, + 0x9b, 0x70, 0x76, 0xba, 0x59, 0xe8, 0x50, 0x8d, 0x8e, 0xa7, 0x58, 0x72, 0xd0, 0x93, 0xb9, 0x8b, + 0xfe, 0xe1, 0x67, 0xdc, 0x98, 0xa9, 0x57, 0xfd, 0x87, 0xb0, 0x6c, 0xfb, 0xce, 0x11, 0xf1, 0x0d, + 0x8b, 0x7a, 0xfb, 0xce, 0x50, 0x5e, 0x81, 0xd5, 0xd4, 0x68, 0x94, 0x03, 0x71, 0x45, 0x08, 0x68, + 0x1c, 0xff, 0x13, 0x5c, 0xf2, 0xd5, 0x67, 0x50, 0x49, 0x6e, 0x75, 0x76, 0x2f, 0x05, 0xce, 0x2f, + 0x12, 0x19, 0x76, 0xf2, 0x20, 0x15, 0x97, 0x18, 0x45, 0x04, 0x9d, 0x6f, 0x43, 0x6e, 0x44, 0x6d, + 0xa1, 0x67, 0xb9, 0x79, 0x9d, 0xc5, 0x1a, 0xff, 0x7c, 0xba, 0x59, 0xa6, 0x41, 0x7d, 0xcb, 0x71, + 0xc9, 0x53, 0x6a, 0x13, 0xcc, 0x01, 0xea, 0x11, 0xe4, 0x98, 0xcf, 0x41, 0x5f, 0x84, 0x5c, 0xb3, + 0xdd, 0x69, 0x29, 0x0b, 0xd5, 0x6b, 0xc7, 0x27, 0xb5, 0x65, 0x6e, 0x12, 0xc6, 0x60, 0x7b, 0x17, + 0x6d, 0x42, 0xe1, 0x59, 0x77, 0x67, 0xf7, 0x29, 0xdb, 0x5e, 0xd7, 0x8f, 0x4f, 0x6a, 0xab, 0x31, + 0x5b, 0x18, 0x0d, 0xbd, 0x0e, 0xf9, 0xc1, 0xd3, 0xde, 0x56, 0x5f, 0xc9, 0x56, 0xd1, 0xf1, 0x49, + 0x6d, 0x25, 0xe6, 0xf3, 0x31, 0x57, 0xaf, 0xc9, 0x55, 0x2d, 0xc5, 0x74, 0xf5, 0x07, 0x19, 0x28, + 0x27, 0x0e, 0x1c, 0xdb, 0x98, 0x2d, 0x7d, 0xab, 0xb1, 0xbb, 0x33, 0x50, 0x16, 0x12, 0x1b, 0x33, + 0x01, 0x69, 0x91, 0x7d, 0x73, 0xe2, 0x32, 0x3f, 0x07, 0x5a, 0xb7, 0xd3, 0x6f, 0xf7, 0x07, 0x7a, + 0x67, 0xa0, 0x64, 0xaa, 0xeb, 0xc7, 0x27, 0xb5, 0xb5, 0x79, 0xf0, 0xd6, 0xc4, 0x75, 0xd9, 0xd6, + 0xd4, 0x1a, 0xda, 0x36, 0xdf, 0xeb, 0xb3, 0xad, 0x99, 0x40, 0x69, 0xa6, 0x75, 0x40, 0x6c, 0xf4, + 0x2e, 0x94, 0x5a, 0xfa, 0x8e, 0xfe, 0xb8, 0xc1, 0xbd, 0x7b, 0xf5, 0xf5, 0xe3, 0x93, 0xda, 0xcd, + 0x57, 0x7b, 0x77, 0xc9, 0xd0, 0x0c, 0x89, 0x3d, 0xb7, 0x45, 0x13, 0x10, 0xf5, 0xbf, 0xb2, 0xb0, + 0x8c, 0x59, 0xb2, 0xec, 0x87, 0x3d, 0xea, 0x3a, 0xd6, 0x14, 0xf5, 0xa0, 0x64, 0x51, 0xcf, 0x76, + 0x12, 0x7e, 0xe2, 0xc1, 0x05, 0x01, 0xd3, 0x4c, 0x2a, 0x6a, 0x69, 0x91, 0x24, 0x9e, 0x29, 0x41, + 0x77, 0x21, 0x6f, 0x13, 0xd7, 0x9c, 0xca, 0xc8, 0xed, 0x66, 0x5d, 0xa4, 0xe3, 0xf5, 0x28, 0x1d, + 0xaf, 0xb7, 0x64, 0x3a, 0x8e, 0x05, 0x8e, 0x67, 0x28, 0xe6, 0x4b, 0xc3, 0x0c, 0x43, 0x32, 0x1a, + 0x87, 0x22, 0x6c, 0xcb, 0xe1, 0xf2, 0xc8, 0x7c, 0xd9, 0x90, 0x24, 0x74, 0x1f, 0x0a, 0x2f, 0x1c, + 0xcf, 0xa6, 0x2f, 0x64, 0x64, 0x76, 0x89, 0x52, 0x09, 0x54, 0x8f, 0x59, 0x48, 0x32, 0x37, 0x4c, + 0xb6, 0x87, 0x3a, 0xdd, 0x8e, 0x1e, 0xed, 0x21, 0xc9, 0xef, 0x7a, 0x1d, 0xea, 0xb1, 0xf3, 0x0f, + 0xdd, 0x8e, 0xb1, 0xd5, 0x68, 0xef, 0xec, 0x62, 0xb6, 0x8f, 0xd6, 0x8e, 0x4f, 0x6a, 0x4a, 0x0c, + 0xd9, 0x32, 0x1d, 0x97, 0xa5, 0x0a, 0x37, 0x61, 0xb1, 0xd1, 0xf9, 0x8e, 0x92, 0xad, 0x2a, 0xc7, + 0x27, 0xb5, 0x4a, 0xcc, 0x6e, 0x78, 0xd3, 0x99, 0xdd, 0xe7, 0xfb, 0x55, 0xff, 0x6e, 0x11, 0x2a, + 0xbb, 0x63, 0xdb, 0x0c, 0x89, 0x38, 0x67, 0xa8, 0x06, 0xe5, 0xb1, 0xe9, 0x9b, 0xae, 0x4b, 0x5c, + 0x27, 0x18, 0xc9, 0x42, 0x43, 0x92, 0x84, 0xde, 0xfb, 0xac, 0x66, 0x6c, 0x16, 0xd9, 0xd9, 0xf9, + 0xde, 0xbf, 0x6e, 0x66, 0x22, 0x83, 0xee, 0xc2, 0xca, 0xbe, 0x18, 0xad, 0x61, 0x5a, 0x7c, 0x61, + 0x17, 0xf9, 0xc2, 0xd6, 0xd3, 0x16, 0x36, 0x39, 0xac, 0xba, 0x9c, 0x64, 0x83, 0x4b, 0xe1, 0xe5, + 0xfd, 0x64, 0x13, 0x3d, 0x84, 0xa5, 0x11, 0xf5, 0x9c, 0x90, 0xfa, 0x57, 0xaf, 0x42, 0x84, 0x44, + 0x77, 0xe0, 0x1a, 0x5b, 0xdc, 0x68, 0x3c, 0x9c, 0xcd, 0xaf, 0xf3, 0x2c, 0x5e, 0x1d, 0x99, 0x2f, + 0x65, 0x87, 0x98, 0x91, 0x51, 0x13, 0xf2, 0xd4, 0x67, 0xf1, 0x62, 0x81, 0x0f, 0xf7, 0xdd, 0x2b, + 0x87, 0x2b, 0x1a, 0x5d, 0x26, 0x83, 0x85, 0xa8, 0xfa, 0x75, 0x58, 0x3e, 0x37, 0x09, 0x16, 0x26, + 0xf5, 0x1a, 0xbb, 0x7d, 0x5d, 0x59, 0x40, 0x15, 0x28, 0x6a, 0xdd, 0xce, 0xa0, 0xdd, 0xd9, 0x65, + 0x71, 0x5e, 0x05, 0x8a, 0xb8, 0xbb, 0xb3, 0xd3, 0x6c, 0x68, 0x4f, 0x94, 0xac, 0x5a, 0x87, 0x72, + 0x42, 0x1b, 0x5a, 0x01, 0xe8, 0x0f, 0xba, 0x3d, 0x63, 0xab, 0x8d, 0xfb, 0x03, 0x11, 0x25, 0xf6, + 0x07, 0x0d, 0x3c, 0x90, 0x84, 0x8c, 0xfa, 0x1f, 0xd9, 0x68, 0x45, 0x65, 0x60, 0xd8, 0x3c, 0x1f, + 0x18, 0x5e, 0x32, 0x78, 0x19, 0x1a, 0xce, 0x1a, 0x71, 0x80, 0xf8, 0x1e, 0x00, 0xdf, 0x38, 0xc4, + 0x36, 0xcc, 0x50, 0x2e, 0x7c, 0xf5, 0x15, 0x23, 0x0f, 0xa2, 0x7a, 0x17, 0x2e, 0x49, 0x74, 0x23, + 0x44, 0xdf, 0x80, 0x8a, 0x45, 0x47, 0x63, 0x97, 0x48, 0xe1, 0xc5, 0x2b, 0x85, 0xcb, 0x31, 0xbe, + 0x11, 0x26, 0x43, 0xd3, 0xdc, 0xf9, 0xe0, 0xf9, 0xd7, 0x32, 0x91, 0x65, 0x52, 0xa2, 0xd1, 0x0a, + 0x14, 0x77, 0x7b, 0xad, 0xc6, 0xa0, 0xdd, 0x79, 0xac, 0x64, 0x10, 0x40, 0x81, 0x9b, 0xba, 0xa5, + 0x64, 0x59, 0x14, 0xad, 0x75, 0x9f, 0xf6, 0x76, 0x74, 0xee, 0xb1, 0xd0, 0x1a, 0x28, 0x91, 0xb1, + 0x0d, 0x6e, 0x48, 0xbd, 0xa5, 0xe4, 0xd0, 0x75, 0x58, 0x8d, 0xa9, 0x52, 0x32, 0x8f, 0x6e, 0x00, + 0x8a, 0x89, 0x33, 0x15, 0x05, 0xf5, 0x97, 0x61, 0x55, 0xa3, 0x5e, 0x68, 0x3a, 0x5e, 0x9c, 0x61, + 0x3c, 0x60, 0x93, 0x96, 0x24, 0xc3, 0x91, 0x75, 0xa2, 0xe6, 0xea, 0xd9, 0xe9, 0x66, 0x39, 0x86, + 0xb6, 0x5b, 0x3c, 0x54, 0x92, 0x0d, 0x9b, 0x9d, 0xdf, 0xb1, 0x63, 0x73, 0xe3, 0xe6, 0x9b, 0x4b, + 0x67, 0xa7, 0x9b, 0x8b, 0xbd, 0x76, 0x0b, 0x33, 0x1a, 0xfa, 0x22, 0x94, 0xc8, 0x4b, 0x27, 0x34, + 0x2c, 0x76, 0x2f, 0x31, 0x03, 0xe6, 0x71, 0x91, 0x11, 0x34, 0x76, 0x0d, 0x35, 0x01, 0x7a, 0xd4, + 0x0f, 0x65, 0xcf, 0x5f, 0x85, 0xfc, 0x98, 0xfa, 0xbc, 0xb2, 0x71, 0x61, 0xbd, 0x8d, 0xc1, 0xc5, + 0x46, 0xc5, 0x02, 0xac, 0xfe, 0xee, 0x22, 0xc0, 0xc0, 0x0c, 0x0e, 0xa5, 0x92, 0x47, 0x50, 0x8a, + 0x6b, 0x97, 0xb2, 0x44, 0x72, 0xe9, 0x6a, 0xc7, 0x60, 0xf4, 0x30, 0xda, 0x6c, 0x22, 0x77, 0x4a, + 0x4d, 0x71, 0xa3, 0x8e, 0xd2, 0xd2, 0x8f, 0xf3, 0x09, 0x12, 0xbb, 0xe6, 0x89, 0xef, 0xcb, 0x95, + 0x67, 0x3f, 0x91, 0xc6, 0xaf, 0x05, 0x61, 0x34, 0x19, 0x7d, 0xa7, 0x16, 0x85, 0xe6, 0x56, 0x64, + 0x7b, 0x01, 0xcf, 0xe4, 0xd0, 0x87, 0x50, 0x66, 0xf3, 0x36, 0x02, 0xce, 0x93, 0x81, 0xf7, 0x85, + 0xa6, 0x12, 0x1a, 0x30, 0x8c, 0x67, 0x56, 0x7e, 0x1d, 0xc0, 0x1c, 0x8f, 0x5d, 0x87, 0xd8, 0xc6, + 0xde, 0x94, 0x47, 0xda, 0x25, 0x5c, 0x92, 0x94, 0xe6, 0x94, 0x1d, 0x97, 0x88, 0x6d, 0x86, 0x3c, + 0x7a, 0xbe, 0xc2, 0x80, 0x12, 0xdd, 0x08, 0x9b, 0x0a, 0xac, 0xf8, 0x13, 0x8f, 0x19, 0x54, 0x8e, + 0x4e, 0xfd, 0x93, 0x2c, 0xbc, 0xd6, 0x21, 0xe1, 0x0b, 0xea, 0x1f, 0x36, 0xc2, 0xd0, 0xb4, 0x0e, + 0x46, 0xc4, 0x93, 0xcb, 0x97, 0x48, 0x68, 0x32, 0xe7, 0x12, 0x9a, 0x75, 0x58, 0x32, 0x5d, 0xc7, + 0x0c, 0x88, 0x08, 0xde, 0x4a, 0x38, 0x6a, 0xb2, 0xb4, 0x8b, 0x25, 0x71, 0x24, 0x08, 0x88, 0xa8, + 0xba, 0xb0, 0x81, 0x47, 0x04, 0xf4, 0x5d, 0xb8, 0x21, 0xc3, 0x34, 0x33, 0xee, 0x8a, 0x25, 0x14, + 0x51, 0xf9, 0x56, 0x4f, 0xcd, 0x2a, 0xd3, 0x07, 0x27, 0xe3, 0xb8, 0x19, 0xb9, 0x3b, 0x0e, 0x65, + 0x54, 0xb8, 0x66, 0xa7, 0xb0, 0xaa, 0x8f, 0xe1, 0xe6, 0x85, 0x22, 0x9f, 0xab, 0xaa, 0xf3, 0x8f, + 0x59, 0x80, 0x76, 0xaf, 0xf1, 0x54, 0x1a, 0xa9, 0x05, 0x85, 0x7d, 0x73, 0xe4, 0xb8, 0xd3, 0xcb, + 0x3c, 0xe0, 0x0c, 0x5f, 0x6f, 0x08, 0x73, 0x6c, 0x71, 0x19, 0x2c, 0x65, 0x79, 0x4e, 0x39, 0xd9, + 0xf3, 0x48, 0x18, 0xe7, 0x94, 0xbc, 0xc5, 0x86, 0xe1, 0x9b, 0x5e, 0xbc, 0x75, 0x45, 0x83, 0x2d, + 0x00, 0x0b, 0x79, 0x5e, 0x98, 0xd3, 0xc8, 0x6d, 0xc9, 0x26, 0xda, 0xe6, 0xb5, 0x53, 0xe2, 0x1f, + 0x11, 0x7b, 0x3d, 0xcf, 0x8d, 0x7a, 0xd5, 0x78, 0xb0, 0x84, 0x0b, 0xdb, 0xc5, 0xd2, 0xd5, 0x0f, + 0x78, 0xc8, 0x34, 0x63, 0x7d, 0x2e, 0x1b, 0xdd, 0x83, 0xe5, 0x73, 0xf3, 0x7c, 0x25, 0x99, 0x6f, + 0xf7, 0x9e, 0x7d, 0x55, 0xc9, 0xc9, 0x5f, 0x5f, 0x57, 0x0a, 0xea, 0xdf, 0x2c, 0x0a, 0x47, 0x23, + 0xad, 0x9a, 0xfe, 0x66, 0x50, 0xe4, 0xbb, 0xdb, 0xa2, 0xae, 0x74, 0x00, 0x6f, 0x5f, 0xee, 0x7f, + 0x58, 0x4e, 0xc7, 0xe1, 0x38, 0x16, 0x44, 0x9b, 0x50, 0x16, 0xbb, 0xd8, 0x60, 0x07, 0x8e, 0x9b, + 0x75, 0x19, 0x83, 0x20, 0x31, 0x49, 0x74, 0x1b, 0x56, 0x78, 0xf1, 0x27, 0x38, 0x20, 0xb6, 0xc0, + 0xe4, 0x38, 0x66, 0x39, 0xa6, 0x72, 0xd8, 0x53, 0xa8, 0x48, 0x82, 0xc1, 0xe3, 0xf9, 0x3c, 0x1f, + 0xd0, 0x9d, 0xab, 0x06, 0x24, 0x44, 0x78, 0x98, 0x5f, 0x1e, 0xcf, 0x1a, 0xea, 0xcf, 0x43, 0x31, + 0x1a, 0x2c, 0x5a, 0x87, 0xc5, 0x81, 0xd6, 0x53, 0x16, 0xaa, 0xab, 0xc7, 0x27, 0xb5, 0x72, 0x44, + 0x1e, 0x68, 0x3d, 0xc6, 0xd9, 0x6d, 0xf5, 0x94, 0xcc, 0x79, 0xce, 0x6e, 0xab, 0x87, 0xaa, 0x90, + 0xeb, 0x6b, 0x83, 0x5e, 0x14, 0x9f, 0x45, 0x2c, 0x46, 0xab, 0xe6, 0x58, 0x7c, 0xa6, 0xee, 0x43, + 0x39, 0xd1, 0x3b, 0x7a, 0x13, 0x96, 0xda, 0x9d, 0xc7, 0x58, 0xef, 0xf7, 0x95, 0x85, 0xea, 0x8d, + 0xe3, 0x93, 0x1a, 0x4a, 0x70, 0xdb, 0xde, 0x90, 0xad, 0x1d, 0x7a, 0x1d, 0x72, 0xdb, 0x5d, 0x76, + 0xef, 0x8b, 0xe4, 0x22, 0x81, 0xd8, 0xa6, 0x41, 0x58, 0xbd, 0x2e, 0x03, 0xbf, 0xa4, 0x62, 0xf5, + 0xf7, 0x32, 0x50, 0x10, 0x07, 0x2d, 0x75, 0x11, 0x1b, 0xb0, 0x14, 0x95, 0x10, 0x44, 0xe2, 0xf7, + 0xf6, 0xc5, 0x49, 0x5a, 0x5d, 0xe6, 0x54, 0x62, 0x6b, 0x46, 0x72, 0xd5, 0xf7, 0xa1, 0x92, 0x64, + 0x7c, 0xae, 0x8d, 0xf9, 0x5d, 0x28, 0xb3, 0xbd, 0x1f, 0x25, 0x6b, 0x0f, 0xa0, 0x20, 0x9c, 0x45, + 0x7c, 0x0f, 0x5d, 0x9c, 0x31, 0x4a, 0x24, 0x7a, 0x04, 0x4b, 0x22, 0xcb, 0x8c, 0xea, 0xca, 0x1b, + 0x97, 0x9f, 0x30, 0x1c, 0xc1, 0xd5, 0x0f, 0x21, 0xd7, 0x23, 0xc4, 0x67, 0xb6, 0xf7, 0xa8, 0x4d, + 0x66, 0x57, 0xb7, 0x4c, 0x90, 0x6d, 0xd2, 0x6e, 0xb1, 0x04, 0xd9, 0x26, 0x6d, 0x3b, 0xae, 0x8d, + 0x65, 0x13, 0xb5, 0xb1, 0x01, 0x54, 0x9e, 0x13, 0x67, 0x78, 0x10, 0x12, 0x9b, 0x2b, 0x7a, 0x17, + 0x72, 0x63, 0x12, 0x0f, 0x7e, 0x3d, 0x75, 0xf3, 0x11, 0xe2, 0x63, 0x8e, 0x62, 0x3e, 0xe6, 0x05, + 0x97, 0x96, 0x8f, 0x21, 0xb2, 0xa5, 0xfe, 0x43, 0x16, 0x56, 0xda, 0x41, 0x30, 0x31, 0x3d, 0x2b, + 0x8a, 0xea, 0xbe, 0x79, 0x3e, 0xaa, 0x4b, 0x7d, 0x35, 0x3a, 0x2f, 0x72, 0xbe, 0xe4, 0x27, 0x6f, + 0xd6, 0x6c, 0x7c, 0xb3, 0xaa, 0x3f, 0xce, 0x44, 0x75, 0xbd, 0xdb, 0x09, 0x57, 0x20, 0x72, 0xc4, + 0xa4, 0x26, 0xb2, 0xeb, 0x1d, 0x7a, 0xf4, 0x85, 0x87, 0xde, 0x80, 0x3c, 0xd6, 0x3b, 0xfa, 0x73, + 0x25, 0x23, 0xb6, 0xe7, 0x39, 0x10, 0x26, 0x1e, 0x79, 0xc1, 0x34, 0xf5, 0xf4, 0x4e, 0x8b, 0x45, + 0x61, 0xd9, 0x14, 0x4d, 0x3d, 0xe2, 0xd9, 0x8e, 0x37, 0x44, 0x6f, 0x42, 0xa1, 0xdd, 0xef, 0xef, + 0xf2, 0x14, 0xf2, 0xb5, 0xe3, 0x93, 0xda, 0xf5, 0x73, 0x28, 0x5e, 0xd3, 0xb5, 0x19, 0x88, 0xa5, + 0x40, 0x2c, 0x3e, 0x4b, 0x01, 0xb1, 0xd8, 0x5a, 0x80, 0x70, 0x77, 0xd0, 0x18, 0xe8, 0x4a, 0x3e, + 0x05, 0x84, 0x29, 0xfb, 0x2b, 0x8f, 0xdb, 0xbf, 0x64, 0x41, 0x69, 0x58, 0x16, 0x19, 0x87, 0x8c, + 0x2f, 0xb3, 0xce, 0x01, 0x14, 0xc7, 0xec, 0x97, 0x43, 0xa2, 0x08, 0xea, 0x51, 0xea, 0xbb, 0xe7, + 0x9c, 0x5c, 0x1d, 0x53, 0x97, 0x34, 0xec, 0x91, 0x13, 0x04, 0x0e, 0xf5, 0x04, 0x0d, 0xc7, 0x9a, + 0xaa, 0xff, 0x99, 0x81, 0xeb, 0x29, 0x08, 0x74, 0x0f, 0x72, 0x3e, 0x75, 0xa3, 0x35, 0xbc, 0x75, + 0x51, 0xc9, 0x96, 0x89, 0x62, 0x8e, 0x44, 0x1b, 0x00, 0xe6, 0x24, 0xa4, 0x26, 0xef, 0x9f, 0xaf, + 0x5e, 0x11, 0x27, 0x28, 0xe8, 0x39, 0x14, 0x02, 0x62, 0xf9, 0x24, 0x8a, 0xb3, 0x3f, 0xfc, 0xbf, + 0x8e, 0xbe, 0xde, 0xe7, 0x6a, 0xb0, 0x54, 0x57, 0xad, 0x43, 0x41, 0x50, 0xd8, 0xb6, 0xb7, 0xcd, + 0xd0, 0x94, 0x05, 0x7d, 0xfe, 0x9b, 0xed, 0x26, 0xd3, 0x1d, 0x46, 0xbb, 0xc9, 0x74, 0x87, 0xea, + 0x5f, 0x67, 0x01, 0xf4, 0x97, 0x21, 0xf1, 0x3d, 0xd3, 0xd5, 0x1a, 0x48, 0x4f, 0xdc, 0x0c, 0x62, + 0xb6, 0x5f, 0x4e, 0x7d, 0xc3, 0x88, 0x25, 0xea, 0x5a, 0x23, 0xe5, 0x6e, 0xb8, 0x09, 0x8b, 0x13, + 0x5f, 0x3e, 0x65, 0x8b, 0x18, 0x79, 0x17, 0xef, 0x60, 0x46, 0x43, 0xfa, 0xcc, 0x6d, 0x2d, 0x5e, + 0xfc, 0x60, 0x9d, 0xe8, 0x20, 0xd5, 0x75, 0xb1, 0x93, 0x6f, 0x99, 0x86, 0x45, 0xe4, 0xad, 0x52, + 0x11, 0x27, 0x5f, 0x6b, 0x68, 0xc4, 0x0f, 0x71, 0xc1, 0x32, 0xd9, 0xff, 0x9f, 0xc8, 0xbf, 0xbd, + 0x0b, 0x30, 0x9b, 0x1a, 0xda, 0x80, 0xbc, 0xb6, 0xd5, 0xef, 0xef, 0x28, 0x0b, 0xc2, 0x81, 0xcf, + 0x58, 0x9c, 0xac, 0xfe, 0x45, 0x16, 0x8a, 0x5a, 0x43, 0x5e, 0xb9, 0x1a, 0x28, 0xdc, 0x2b, 0xf1, + 0x67, 0x10, 0xf2, 0x72, 0xec, 0xf8, 0x53, 0xe9, 0x58, 0x2e, 0x49, 0x78, 0x57, 0x98, 0x08, 0x1b, + 0xb5, 0xce, 0x05, 0x10, 0x86, 0x0a, 0x91, 0x46, 0x30, 0x2c, 0x33, 0xf2, 0xf1, 0x1b, 0x97, 0x1b, + 0x4b, 0xa4, 0x2e, 0xb3, 0x76, 0x80, 0xcb, 0x91, 0x12, 0xcd, 0x0c, 0xd0, 0x7b, 0xb0, 0x1a, 0x38, + 0x43, 0xcf, 0xf1, 0x86, 0x46, 0x64, 0x3c, 0xfe, 0x26, 0xd3, 0xbc, 0x76, 0x76, 0xba, 0xb9, 0xdc, + 0x17, 0x2c, 0x69, 0xc3, 0x65, 0x89, 0xd4, 0xb8, 0x29, 0xd1, 0xd7, 0x61, 0x25, 0x21, 0xca, 0xac, + 0x28, 0xcc, 0xae, 0x9c, 0x9d, 0x6e, 0x56, 0x62, 0xc9, 0x27, 0x64, 0x8a, 0x2b, 0xb1, 0xe0, 0x13, + 0xc2, 0x6b, 0x33, 0xfb, 0xd4, 0xb7, 0x88, 0xe1, 0xf3, 0x33, 0xcd, 0x6f, 0xf7, 0x1c, 0x2e, 0x73, + 0x9a, 0x38, 0xe6, 0xea, 0x33, 0xb8, 0xde, 0xf5, 0xad, 0x03, 0x12, 0x84, 0xc2, 0x14, 0xd2, 0x8a, + 0x1f, 0xc2, 0xad, 0xd0, 0x0c, 0x0e, 0x8d, 0x03, 0x27, 0x08, 0xa9, 0x3f, 0x35, 0x7c, 0x12, 0x12, + 0x8f, 0xf1, 0x0d, 0xfe, 0xcc, 0x2b, 0x0b, 0x82, 0x37, 0x19, 0x66, 0x5b, 0x40, 0x70, 0x84, 0xd8, + 0x61, 0x00, 0xb5, 0x0d, 0x15, 0x96, 0xc2, 0xc8, 0xa2, 0x1a, 0x9b, 0x3d, 0xb8, 0x74, 0x68, 0x7c, + 0xe6, 0x6b, 0xaa, 0xe4, 0xd2, 0xa1, 0xf8, 0xa9, 0x7e, 0x1b, 0x94, 0x96, 0x13, 0x8c, 0xcd, 0xd0, + 0x3a, 0x88, 0x2a, 0x9d, 0xa8, 0x05, 0xca, 0x01, 0x31, 0xfd, 0x70, 0x8f, 0x98, 0xa1, 0x31, 0x26, + 0xbe, 0x43, 0xed, 0xab, 0x57, 0x79, 0x35, 0x16, 0xe9, 0x71, 0x09, 0xf5, 0xbf, 0x33, 0x00, 0xd8, + 0xdc, 0x8f, 0xa2, 0xb5, 0xaf, 0xc0, 0xb5, 0xc0, 0x33, 0xc7, 0xc1, 0x01, 0x0d, 0x0d, 0xc7, 0x0b, + 0x89, 0x7f, 0x64, 0xba, 0xb2, 0xb8, 0xa3, 0x44, 0x8c, 0xb6, 0xa4, 0xa3, 0x77, 0x01, 0x1d, 0x12, + 0x32, 0x36, 0xa8, 0x6b, 0x1b, 0x11, 0x53, 0x3c, 0x42, 0xe7, 0xb0, 0xc2, 0x38, 0x5d, 0xd7, 0xee, + 0x47, 0x74, 0xd4, 0x84, 0x0d, 0x36, 0x7d, 0xe2, 0x85, 0xbe, 0x43, 0x02, 0x63, 0x9f, 0xfa, 0x46, + 0xe0, 0xd2, 0x17, 0xc6, 0x3e, 0x75, 0x5d, 0xfa, 0x82, 0xf8, 0x51, 0xdd, 0xac, 0xea, 0xd2, 0xa1, + 0x2e, 0x40, 0x5b, 0xd4, 0xef, 0xbb, 0xf4, 0xc5, 0x56, 0x84, 0x60, 0x21, 0xdd, 0x6c, 0xce, 0xa1, + 0x63, 0x1d, 0x46, 0x21, 0x5d, 0x4c, 0x1d, 0x38, 0xd6, 0x21, 0x7a, 0x13, 0x96, 0x89, 0x4b, 0x78, + 0xf9, 0x44, 0xa0, 0xf2, 0x1c, 0x55, 0x89, 0x88, 0x0c, 0xa4, 0x7e, 0x04, 0x8a, 0xee, 0x59, 0xfe, + 0x74, 0x9c, 0x58, 0xf3, 0x77, 0x01, 0x31, 0x27, 0x69, 0xb8, 0xd4, 0x3a, 0x34, 0x46, 0xa6, 0x67, + 0x0e, 0xd9, 0xb8, 0xc4, 0xeb, 0x9f, 0xc2, 0x38, 0x3b, 0xd4, 0x3a, 0x7c, 0x2a, 0xe9, 0xea, 0x7b, + 0x00, 0xfd, 0xb1, 0x4f, 0x4c, 0xbb, 0xcb, 0xa2, 0x09, 0x66, 0x3a, 0xde, 0x32, 0x6c, 0xf9, 0xb6, + 0x4a, 0x7d, 0x79, 0xd4, 0x15, 0xc1, 0x68, 0xc5, 0x74, 0xf5, 0x67, 0xe1, 0x7a, 0xcf, 0x35, 0x2d, + 0xfe, 0x9d, 0x41, 0x2f, 0x7e, 0xce, 0x42, 0x8f, 0xa0, 0x20, 0xa0, 0x72, 0x25, 0x53, 0x8f, 0xdb, + 0xac, 0xcf, 0xed, 0x05, 0x2c, 0xf1, 0xcd, 0x0a, 0xc0, 0x4c, 0x8f, 0xfa, 0x67, 0x19, 0x28, 0xc5, + 0xfa, 0x51, 0x4d, 0xbc, 0xd2, 0x84, 0xbe, 0xe9, 0x78, 0x32, 0xe3, 0x2f, 0xe1, 0x24, 0x09, 0xb5, + 0xa1, 0x3c, 0x8e, 0xa5, 0x2f, 0x8d, 0xe7, 0x52, 0x46, 0x8d, 0x93, 0xb2, 0xe8, 0x7d, 0x28, 0x45, + 0x8f, 0xd9, 0x91, 0x87, 0xbd, 0xfc, 0xed, 0x7b, 0x06, 0x57, 0xbf, 0x09, 0xf0, 0x2d, 0xea, 0x78, + 0x03, 0x7a, 0x48, 0x3c, 0xfe, 0xfc, 0xca, 0xf2, 0x45, 0x12, 0x59, 0x51, 0xb6, 0x78, 0x19, 0x40, + 0x2c, 0x41, 0xfc, 0x0a, 0x29, 0x9a, 0xea, 0x5f, 0x65, 0xa1, 0x80, 0x29, 0x0d, 0xb5, 0x06, 0xaa, + 0x41, 0x41, 0xfa, 0x09, 0x7e, 0xff, 0x34, 0x4b, 0x67, 0xa7, 0x9b, 0x79, 0xe1, 0x20, 0xf2, 0x16, + 0xf7, 0x0c, 0x09, 0x0f, 0x9e, 0xbd, 0xc8, 0x83, 0xa3, 0x7b, 0x50, 0x91, 0x20, 0xe3, 0xc0, 0x0c, + 0x0e, 0x44, 0xf2, 0xd6, 0x5c, 0x39, 0x3b, 0xdd, 0x04, 0x81, 0xdc, 0x36, 0x83, 0x03, 0x0c, 0x02, + 0xcd, 0x7e, 0x23, 0x1d, 0xca, 0x1f, 0x53, 0xc7, 0x33, 0x42, 0x3e, 0x09, 0x59, 0x68, 0x4c, 0x5d, + 0xc7, 0xd9, 0x54, 0xe5, 0x97, 0x0a, 0xf0, 0xf1, 0x6c, 0xf2, 0x3a, 0x2c, 0xfb, 0x94, 0x86, 0xc2, + 0x6d, 0x39, 0xd4, 0x93, 0x35, 0x8c, 0x5a, 0x6a, 0x69, 0x9b, 0xd2, 0x10, 0x4b, 0x1c, 0xae, 0xf8, + 0x89, 0x16, 0xba, 0x07, 0x6b, 0xae, 0x19, 0x84, 0x06, 0xf7, 0x77, 0xf6, 0x4c, 0x5b, 0x81, 0x1f, + 0x35, 0xc4, 0x78, 0x5b, 0x9c, 0x15, 0x49, 0xa8, 0xff, 0x94, 0x81, 0x32, 0x9b, 0x8c, 0xb3, 0xef, + 0x58, 0x2c, 0xc8, 0xfb, 0xfc, 0xb1, 0xc7, 0x4d, 0x58, 0xb4, 0x02, 0x5f, 0x1a, 0x95, 0x5f, 0xbe, + 0x5a, 0x1f, 0x63, 0x46, 0x43, 0x1f, 0x41, 0x41, 0xd6, 0x52, 0x44, 0xd8, 0xa1, 0x5e, 0x1d, 0x8e, + 0x4a, 0xdb, 0x48, 0x39, 0xbe, 0x97, 0x67, 0xa3, 0x13, 0x97, 0x00, 0x4e, 0x92, 0xd0, 0x0d, 0xc8, + 0x5a, 0xc2, 0x5c, 0xf2, 0x53, 0x18, 0xad, 0x83, 0xb3, 0x96, 0xa7, 0xfe, 0x20, 0x03, 0xcb, 0xb3, + 0x03, 0xcf, 0x76, 0xc0, 0x2d, 0x28, 0x05, 0x93, 0xbd, 0x60, 0x1a, 0x84, 0x64, 0x14, 0x3d, 0x2d, + 0xc7, 0x04, 0xd4, 0x86, 0x92, 0xe9, 0x0e, 0xa9, 0xef, 0x84, 0x07, 0x23, 0x99, 0xa5, 0xa6, 0x87, + 0x0a, 0x49, 0x9d, 0xf5, 0x46, 0x24, 0x82, 0x67, 0xd2, 0xd1, 0xbd, 0x2f, 0xbe, 0x3f, 0xe0, 0xf7, + 0xfe, 0x1b, 0x50, 0x71, 0xcd, 0x11, 0x2f, 0x2e, 0x85, 0xce, 0x48, 0xcc, 0x23, 0x87, 0xcb, 0x92, + 0x36, 0x70, 0x46, 0x44, 0x55, 0xa1, 0x14, 0x2b, 0x43, 0xab, 0x50, 0x6e, 0xe8, 0x7d, 0xe3, 0xfe, + 0x83, 0x47, 0xc6, 0x63, 0xed, 0xa9, 0xb2, 0x20, 0x63, 0xd3, 0x3f, 0xcf, 0xc0, 0xb2, 0x74, 0x47, + 0x32, 0xde, 0x7f, 0x13, 0x96, 0x7c, 0x73, 0x3f, 0x8c, 0x32, 0x92, 0x9c, 0xd8, 0xd5, 0xcc, 0xc3, + 0xb3, 0x8c, 0x84, 0xb1, 0xd2, 0x33, 0x92, 0xc4, 0xc7, 0x0e, 0x8b, 0x97, 0x7e, 0xec, 0x90, 0xfb, + 0xa9, 0x7c, 0xec, 0xa0, 0xfe, 0x0a, 0xc0, 0x96, 0xe3, 0x92, 0x81, 0xa8, 0x43, 0xa5, 0xe5, 0x97, + 0x2c, 0x86, 0x93, 0x75, 0xce, 0x28, 0x86, 0x6b, 0xb7, 0x30, 0xa3, 0x31, 0xd6, 0xd0, 0xb1, 0xe5, + 0x61, 0xe4, 0xac, 0xc7, 0x8c, 0x35, 0x74, 0xec, 0xf8, 0x55, 0x2e, 0x77, 0xd5, 0xab, 0xdc, 0x49, + 0x06, 0x56, 0x65, 0xec, 0x1a, 0xbb, 0xdf, 0x2f, 0x43, 0x49, 0x84, 0xb1, 0xb3, 0x84, 0x8e, 0x3f, + 0xf0, 0x0b, 0x5c, 0xbb, 0x85, 0x8b, 0x82, 0xdd, 0xb6, 0xd1, 0x26, 0x94, 0x25, 0x34, 0xf1, 0xd9, + 0x14, 0x08, 0x52, 0x87, 0x0d, 0xff, 0xab, 0x90, 0xdb, 0x77, 0x5c, 0x22, 0x37, 0x7a, 0xaa, 0x03, + 0x98, 0x19, 0x60, 0x7b, 0x01, 0x73, 0x74, 0xb3, 0x18, 0x15, 0xea, 0xf8, 0xf8, 0x64, 0xda, 0x99, + 0x1c, 0x9f, 0xc8, 0x40, 0xe7, 0xc6, 0x27, 0x70, 0x6c, 0x7c, 0x82, 0x2d, 0xc6, 0x27, 0xa1, 0xc9, + 0xf1, 0x09, 0xd2, 0x4f, 0x65, 0x7c, 0x3b, 0x70, 0xa3, 0xe9, 0x9a, 0xd6, 0xa1, 0xeb, 0x04, 0x21, + 0xb1, 0x93, 0x1e, 0xe3, 0x01, 0x14, 0xce, 0x05, 0x9d, 0x97, 0x55, 0x34, 0x25, 0x52, 0xfd, 0xf7, + 0x0c, 0x54, 0xb6, 0x89, 0xe9, 0x86, 0x07, 0xb3, 0xb2, 0x51, 0x48, 0x82, 0x50, 0x5e, 0x56, 0xfc, + 0x37, 0xfa, 0x1a, 0x14, 0xe3, 0x98, 0xe4, 0xca, 0xb7, 0xb9, 0x18, 0x8a, 0x1e, 0xc2, 0x12, 0x3b, + 0x63, 0x74, 0x12, 0x25, 0x3b, 0x97, 0x3d, 0xfb, 0x48, 0x24, 0xbb, 0x64, 0x7c, 0xc2, 0x83, 0x10, + 0xbe, 0x95, 0xf2, 0x38, 0x6a, 0xa2, 0xff, 0x0f, 0x15, 0xfe, 0x6a, 0x11, 0xc5, 0x5c, 0xf9, 0xab, + 0x74, 0x96, 0xc5, 0xc3, 0xa3, 0x88, 0xb7, 0xfe, 0x38, 0x0b, 0x6b, 0x4f, 0xcd, 0xe9, 0x1e, 0x91, + 0x6e, 0x83, 0xd8, 0x98, 0x58, 0xd4, 0xb7, 0x51, 0x2f, 0xe9, 0x6e, 0x2e, 0x79, 0xc7, 0x4c, 0x13, + 0x4e, 0xf7, 0x3a, 0x51, 0x02, 0x96, 0x4d, 0x24, 0x60, 0x6b, 0x90, 0xf7, 0xa8, 0x67, 0x11, 0xe9, + 0x8b, 0x44, 0x43, 0xfd, 0xed, 0x4c, 0xd2, 0xd7, 0x54, 0xe3, 0x37, 0x46, 0x5e, 0x81, 0xea, 0xd0, + 0x30, 0xee, 0x0e, 0x7d, 0x04, 0xd5, 0xbe, 0xae, 0x61, 0x7d, 0xd0, 0xec, 0x7e, 0xdb, 0xe8, 0x37, + 0x76, 0xfa, 0x8d, 0x07, 0xf7, 0x8c, 0x5e, 0x77, 0xe7, 0x3b, 0xf7, 0x1f, 0xde, 0xfb, 0x9a, 0x92, + 0xa9, 0xd6, 0x8e, 0x4f, 0x6a, 0xb7, 0x3a, 0x0d, 0x6d, 0x47, 0x1c, 0x99, 0x3d, 0xfa, 0xb2, 0x6f, + 0xba, 0x81, 0xf9, 0xe0, 0x5e, 0x8f, 0xba, 0x53, 0x86, 0x41, 0x5f, 0x01, 0xb4, 0xa5, 0xe3, 0x8e, + 0x3e, 0x30, 0x22, 0x87, 0xa6, 0x35, 0x35, 0x25, 0x2b, 0xd2, 0x9a, 0x2d, 0xe2, 0x7b, 0x24, 0x6c, + 0xe8, 0xfd, 0xfb, 0x0f, 0x1e, 0x69, 0x4d, 0x8d, 0x1d, 0x82, 0x4a, 0xf2, 0x76, 0x4b, 0x5e, 0xda, + 0x99, 0x0b, 0x2f, 0xed, 0xd9, 0xdd, 0x9f, 0xbd, 0xe0, 0xee, 0xdf, 0x82, 0x35, 0xcb, 0xa7, 0x41, + 0x60, 0xb0, 0x5c, 0x81, 0xd8, 0x73, 0xd9, 0xc8, 0x17, 0xce, 0x4e, 0x37, 0xaf, 0x69, 0x8c, 0xdf, + 0xe7, 0x6c, 0xa9, 0xfe, 0x9a, 0x95, 0x20, 0xf1, 0x9e, 0xd4, 0xdf, 0x5f, 0x64, 0x61, 0x97, 0x73, + 0xe4, 0xb8, 0x64, 0x48, 0x02, 0xf4, 0x0c, 0x56, 0x2d, 0x9f, 0xd8, 0x2c, 0x09, 0x30, 0xdd, 0xe4, + 0xc7, 0xba, 0xff, 0x2f, 0x35, 0x02, 0x8a, 0x05, 0xeb, 0x5a, 0x2c, 0xd5, 0x1f, 0x13, 0x0b, 0xaf, + 0x58, 0xe7, 0xda, 0xe8, 0x63, 0x58, 0x0d, 0x88, 0xeb, 0x78, 0x93, 0x97, 0x86, 0x45, 0xbd, 0x90, + 0xbc, 0x8c, 0xde, 0xd6, 0xae, 0xd2, 0xdb, 0xd7, 0x77, 0x98, 0x94, 0x26, 0x84, 0x9a, 0xe8, 0xec, + 0x74, 0x73, 0xe5, 0x3c, 0x0d, 0xaf, 0x48, 0xcd, 0xb2, 0x5d, 0xed, 0xc0, 0xca, 0xf9, 0xd1, 0xa0, + 0x35, 0xe9, 0x29, 0xb8, 0xc3, 0x89, 0x3c, 0x01, 0xba, 0x05, 0x45, 0x9f, 0x0c, 0x9d, 0x20, 0xf4, + 0x85, 0x99, 0x19, 0x27, 0xa6, 0x30, 0x3f, 0x21, 0xbe, 0xa5, 0xaa, 0xfe, 0x12, 0xcc, 0xf5, 0xc8, + 0x8e, 0x96, 0xed, 0x04, 0xe6, 0x9e, 0x54, 0x59, 0xc4, 0x51, 0x93, 0xed, 0xd8, 0x49, 0x10, 0x87, + 0x75, 0xfc, 0x37, 0xa3, 0xf1, 0xf8, 0x43, 0x7e, 0x59, 0xc6, 0x23, 0x8c, 0xe8, 0x03, 0xd6, 0x5c, + 0xe2, 0x03, 0xd6, 0x35, 0xc8, 0xbb, 0xe4, 0x88, 0xb8, 0xe2, 0xe6, 0xc7, 0xa2, 0x71, 0xe7, 0x1e, + 0x54, 0xa2, 0x2f, 0x25, 0xf9, 0x37, 0x18, 0x45, 0xc8, 0x0d, 0x1a, 0xfd, 0x27, 0xca, 0x02, 0x02, + 0x28, 0x88, 0x9d, 0x2c, 0xde, 0xfd, 0xb4, 0x6e, 0x67, 0xab, 0xfd, 0x58, 0xc9, 0xde, 0xf9, 0x9d, + 0x1c, 0x94, 0xe2, 0x97, 0x27, 0x76, 0xd3, 0x74, 0xf4, 0xe7, 0xd1, 0x51, 0x88, 0xe9, 0x1d, 0xf2, + 0x02, 0xbd, 0x31, 0xab, 0x59, 0x7d, 0x24, 0x9e, 0xda, 0x63, 0x76, 0x54, 0xaf, 0x7a, 0x0b, 0x8a, + 0x8d, 0x7e, 0xbf, 0xfd, 0xb8, 0xa3, 0xb7, 0x94, 0x4f, 0x33, 0xd5, 0x2f, 0x1c, 0x9f, 0xd4, 0xae, + 0xc5, 0xa0, 0x46, 0x20, 0x36, 0x1f, 0x47, 0x69, 0x9a, 0xde, 0x1b, 0xe8, 0x2d, 0xe5, 0x93, 0xec, + 0x3c, 0x8a, 0xd7, 0x60, 0xf8, 0x47, 0x40, 0xa5, 0x1e, 0xd6, 0x7b, 0x0d, 0xcc, 0x3a, 0xfc, 0x34, + 0x2b, 0x4a, 0x69, 0xb3, 0x1e, 0x7d, 0x32, 0x36, 0x7d, 0xd6, 0xe7, 0x46, 0xf4, 0x55, 0xdd, 0x27, + 0x8b, 0xe2, 0x43, 0x91, 0xd9, 0x33, 0x1a, 0x31, 0xed, 0x29, 0xeb, 0x8d, 0xbf, 0x5f, 0x72, 0x35, + 0x8b, 0x73, 0xbd, 0xf5, 0x99, 0xa7, 0x62, 0x5a, 0x54, 0x58, 0xc2, 0xbb, 0x9d, 0x0e, 0x03, 0x7d, + 0x92, 0x9b, 0x9b, 0x1d, 0x9e, 0x78, 0x2c, 0xbf, 0x46, 0xb7, 0xa1, 0x18, 0x3d, 0x6f, 0x2a, 0x9f, + 0xe6, 0xe6, 0x06, 0xa4, 0x45, 0x6f, 0xb3, 0xbc, 0xc3, 0xed, 0xdd, 0x01, 0xff, 0xe8, 0xef, 0x93, + 0xfc, 0x7c, 0x87, 0x07, 0x93, 0xd0, 0xa6, 0x2f, 0x3c, 0x76, 0x66, 0x65, 0xd5, 0xee, 0xd3, 0xbc, + 0xf0, 0x05, 0x31, 0x46, 0x96, 0xec, 0xde, 0x82, 0x22, 0xd6, 0xbf, 0x25, 0xbe, 0x0f, 0xfc, 0xa4, + 0x30, 0xa7, 0x07, 0x93, 0x8f, 0x89, 0xc5, 0x7a, 0xab, 0x41, 0x01, 0xeb, 0x4f, 0xbb, 0xcf, 0x74, + 0xe5, 0x0f, 0x0a, 0x73, 0x7a, 0x30, 0x19, 0x51, 0xfe, 0x95, 0x54, 0xb1, 0x8b, 0x7b, 0xdb, 0x0d, + 0xbe, 0x28, 0xf3, 0x7a, 0xba, 0xfe, 0xf8, 0xc0, 0xf4, 0x88, 0x3d, 0xfb, 0x9e, 0x26, 0x66, 0xdd, + 0xf9, 0x39, 0x28, 0x46, 0x91, 0x2e, 0xda, 0x80, 0xc2, 0xf3, 0x2e, 0x7e, 0xa2, 0x63, 0x65, 0x41, + 0x58, 0x39, 0xe2, 0x3c, 0x17, 0x39, 0x4a, 0x0d, 0x96, 0x9e, 0x36, 0x3a, 0x8d, 0xc7, 0x3a, 0x8e, + 0x4a, 0xee, 0x11, 0x40, 0x86, 0x6b, 0x55, 0x45, 0x76, 0x10, 0xeb, 0x6c, 0xae, 0x7f, 0xff, 0x47, + 0x1b, 0x0b, 0x3f, 0xfc, 0xd1, 0xc6, 0xc2, 0x27, 0x67, 0x1b, 0x99, 0xef, 0x9f, 0x6d, 0x64, 0xfe, + 0xfe, 0x6c, 0x23, 0xf3, 0x6f, 0x67, 0x1b, 0x99, 0xbd, 0x02, 0xbf, 0x54, 0x1e, 0xfe, 0x6f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x80, 0x94, 0x1b, 0x9c, 0x7a, 0x32, 0x00, 0x00, } diff --git a/api/types.proto b/api/types.proto index 73c202f0be..dc512b503f 100644 --- a/api/types.proto +++ b/api/types.proto @@ -125,6 +125,9 @@ message NodeDescription { // Information on the node's TLS setup NodeTLSInfo tls_info = 5 [(gogoproto.customname) = "TLSInfo"]; + + // FIPS indicates whether the node has FIPS-enabled + bool fips = 6 [(gogoproto.customname) = "FIPS"]; } message NodeTLSInfo { diff --git a/node/node.go b/node/node.go index 3e37f869c9..a6ab3035af 100644 --- a/node/node.go +++ b/node/node.go @@ -528,6 +528,7 @@ waitPeer: CertIssuerPublicKey: issuer.PublicKey, CertIssuerSubject: issuer.Subject, }, + FIPS: n.config.FIPS, } // if a join address has been specified, then if the agent fails to connect due to a TLS error, fail fast - don't // keep re-trying to join From 0e8fc3a959ea98c38560d4b4eac0c19cb16f1cd2 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 3 Apr 2018 12:05:34 -0700 Subject: [PATCH 12/15] Add a FIPS boolean to the cluster object and the node description. When a cluster is first created, the FIPS value should be set and it should not be changed through the lifetime of the cluster, because converting from non-FIPS to FIPS should not be possible (to avoid compliance issues, even if there were a migration process, we'd have to provide a validation tool to ensure that the migration was complete across the cluster). Signed-off-by: Ying Li (cherry picked from commit 6197cc50c44dd553547b6519e4b5b927d0d602a3) --- api/api.pb.txt | 10 ++ api/objects.pb.go | 234 ++++++++++++++++++++++++++------------------- api/objects.proto | 6 ++ manager/manager.go | 12 ++- 4 files changed, 163 insertions(+), 99 deletions(-) diff --git a/api/api.pb.txt b/api/api.pb.txt index 5441cdb4a4..d81acb202b 100755 --- a/api/api.pb.txt +++ b/api/api.pb.txt @@ -6167,6 +6167,16 @@ file { type_name: ".docker.swarmkit.v1.EncryptionKey" json_name: "unlockKeys" } + field { + name: "fips" + number: 10 + label: LABEL_OPTIONAL + type: TYPE_BOOL + options { + 65004: "FIPS" + } + json_name: "fips" + } nested_type { name: "BlacklistedCertificatesEntry" field { diff --git a/api/objects.pb.go b/api/objects.pb.go index 01abbe5076..a7d8d49b8c 100644 --- a/api/objects.pb.go +++ b/api/objects.pb.go @@ -270,6 +270,11 @@ type Cluster struct { // If the key is empty, the node will be unlocked (will not require a key // to start up from a shut down state). UnlockKeys []*EncryptionKey `protobuf:"bytes,9,rep,name=unlock_keys,json=unlockKeys" json:"unlock_keys,omitempty"` + // FIPS specifies whether this cluster should be in FIPS mode. This changes + // the format of the join tokens, and nodes that are not FIPS-enabled should + // reject joining the cluster. Nodes that report themselves to be non-FIPS + // should be rejected from the cluster. + FIPS bool `protobuf:"varint,10,opt,name=fips,proto3" json:"fips,omitempty"` } func (m *Cluster) Reset() { *m = Cluster{} } @@ -1426,6 +1431,16 @@ func (m *Cluster) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.FIPS { + dAtA[i] = 0x50 + i++ + if m.FIPS { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -1925,6 +1940,9 @@ func (m *Cluster) Size() (n int) { n += 1 + l + sovObjects(uint64(l)) } } + if m.FIPS { + n += 2 + } return n } @@ -4543,6 +4561,7 @@ func (this *Cluster) String() string { `EncryptionKeyLamportClock:` + fmt.Sprintf("%v", this.EncryptionKeyLamportClock) + `,`, `BlacklistedCertificates:` + mapStringForBlacklistedCertificates + `,`, `UnlockKeys:` + strings.Replace(fmt.Sprintf("%v", this.UnlockKeys), "EncryptionKey", "EncryptionKey", 1) + `,`, + `FIPS:` + fmt.Sprintf("%v", this.FIPS) + `,`, `}`, }, "") return s @@ -6956,6 +6975,26 @@ func (m *Cluster) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FIPS", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowObjects + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FIPS = bool(v != 0) default: iNdEx = preIndex skippy, err := skipObjects(dAtA[iNdEx:]) @@ -7752,101 +7791,102 @@ var ( func init() { proto.RegisterFile("github.com/docker/swarmkit/api/objects.proto", fileDescriptorObjects) } var fileDescriptorObjects = []byte{ - // 1527 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcf, 0x6f, 0x1b, 0x4f, - 0x15, 0xef, 0xda, 0x1b, 0xff, 0x78, 0x4e, 0x4c, 0x98, 0x86, 0xb0, 0x35, 0xc1, 0x0e, 0xae, 0x40, - 0x55, 0x55, 0x39, 0x25, 0x14, 0x48, 0x03, 0xa5, 0xb5, 0x93, 0xa8, 0xb5, 0x4a, 0x69, 0x34, 0x2d, - 0x2d, 0xb7, 0x65, 0xb2, 0x3b, 0x75, 0x17, 0xaf, 0x77, 0x56, 0x3b, 0x63, 0x17, 0xdf, 0x7a, 0x0e, - 0x7f, 0x40, 0x6e, 0x1c, 0xfa, 0x37, 0x70, 0xe1, 0xc2, 0x81, 0x53, 0x8f, 0x9c, 0x10, 0xa7, 0x88, - 0xfa, 0xbf, 0x40, 0xe2, 0x80, 0x66, 0x76, 0xd6, 0xde, 0xc4, 0x9b, 0x5f, 0xa8, 0x8a, 0xbe, 0xa7, - 0xcc, 0xec, 0x7c, 0x3e, 0xef, 0xd7, 0xbc, 0xf7, 0xe6, 0xc5, 0x70, 0xaf, 0xe7, 0x89, 0xf7, 0xc3, - 0x83, 0x96, 0xc3, 0x06, 0x1b, 0x2e, 0x73, 0xfa, 0x34, 0xda, 0xe0, 0x1f, 0x48, 0x34, 0xe8, 0x7b, - 0x62, 0x83, 0x84, 0xde, 0x06, 0x3b, 0xf8, 0x03, 0x75, 0x04, 0x6f, 0x85, 0x11, 0x13, 0x0c, 0xa1, - 0x18, 0xd2, 0x4a, 0x20, 0xad, 0xd1, 0x8f, 0x6b, 0x77, 0x2f, 0x90, 0x20, 0xc6, 0x21, 0xd5, 0xfc, - 0x0b, 0xb1, 0x3c, 0xa4, 0x4e, 0x82, 0x6d, 0xf4, 0x18, 0xeb, 0xf9, 0x74, 0x43, 0xed, 0x0e, 0x86, - 0xef, 0x36, 0x84, 0x37, 0xa0, 0x5c, 0x90, 0x41, 0xa8, 0x01, 0x2b, 0x3d, 0xd6, 0x63, 0x6a, 0xb9, - 0x21, 0x57, 0xfa, 0xeb, 0xad, 0xd3, 0x34, 0x12, 0x8c, 0xf5, 0xd1, 0xcf, 0xcf, 0xd1, 0x3e, 0x85, - 0x87, 0xfe, 0xb0, 0xe7, 0x05, 0xfa, 0x4f, 0x4c, 0x6c, 0xfe, 0xd5, 0x00, 0xf3, 0x05, 0x15, 0x04, - 0xfd, 0x02, 0x8a, 0x23, 0x1a, 0x71, 0x8f, 0x05, 0x96, 0xb1, 0x6e, 0xdc, 0xa9, 0x6c, 0x7e, 0xaf, - 0x35, 0x1f, 0x91, 0xd6, 0x9b, 0x18, 0xd2, 0x31, 0x3f, 0x1f, 0x37, 0x6e, 0xe0, 0x84, 0x81, 0x1e, - 0x02, 0x38, 0x11, 0x25, 0x82, 0xba, 0x36, 0x11, 0x56, 0x4e, 0xf1, 0x6b, 0xad, 0xd8, 0xdc, 0x56, - 0xa2, 0xbf, 0xf5, 0x3a, 0xf1, 0x12, 0x97, 0x35, 0xba, 0x2d, 0x24, 0x75, 0x18, 0xba, 0x09, 0x35, - 0x7f, 0x31, 0x55, 0xa3, 0xdb, 0xa2, 0xf9, 0x71, 0x01, 0xcc, 0xdf, 0x30, 0x97, 0xa2, 0x55, 0xc8, - 0x79, 0xae, 0x32, 0xbb, 0xdc, 0x29, 0x4c, 0x8e, 0x1b, 0xb9, 0xee, 0x2e, 0xce, 0x79, 0x2e, 0xda, - 0x04, 0x73, 0x40, 0x05, 0xd1, 0x06, 0x59, 0x59, 0x0e, 0x49, 0xdf, 0xb5, 0x37, 0x0a, 0x8b, 0x7e, - 0x06, 0xa6, 0xbc, 0x2a, 0x6d, 0xc9, 0x5a, 0x16, 0x47, 0xea, 0x7c, 0x15, 0x52, 0x27, 0xe1, 0x49, - 0x3c, 0xda, 0x83, 0x8a, 0x4b, 0xb9, 0x13, 0x79, 0xa1, 0x90, 0x31, 0x34, 0x15, 0xfd, 0xf6, 0x59, - 0xf4, 0xdd, 0x19, 0x14, 0xa7, 0x79, 0xe8, 0x97, 0x50, 0xe0, 0x82, 0x88, 0x21, 0xb7, 0x16, 0x94, - 0x84, 0xfa, 0x99, 0x06, 0x28, 0x94, 0x36, 0x41, 0x73, 0xd0, 0x33, 0xa8, 0x0e, 0x48, 0x40, 0x7a, - 0x34, 0xb2, 0xb5, 0x94, 0x82, 0x92, 0xf2, 0x83, 0x4c, 0xd7, 0x63, 0x64, 0x2c, 0x08, 0x2f, 0x0d, - 0xd2, 0x5b, 0xd4, 0x05, 0x20, 0x42, 0x10, 0xe7, 0xfd, 0x80, 0x06, 0xc2, 0x2a, 0x2a, 0x29, 0x3f, - 0xcc, 0xb4, 0x85, 0x8a, 0x0f, 0x2c, 0xea, 0xb7, 0xa7, 0xe0, 0x4e, 0xce, 0x32, 0x70, 0x8a, 0x8c, - 0x9e, 0x42, 0xc5, 0xa1, 0x91, 0xf0, 0xde, 0x79, 0x0e, 0x11, 0xd4, 0x2a, 0x29, 0x59, 0x8d, 0x2c, - 0x59, 0x3b, 0x33, 0x98, 0x76, 0x2c, 0xcd, 0x44, 0xf7, 0xc1, 0x8c, 0x98, 0x4f, 0xad, 0xf2, 0xba, - 0x71, 0xa7, 0x7a, 0xf6, 0xd5, 0x60, 0xe6, 0x53, 0xac, 0x90, 0x52, 0xf5, 0xcc, 0x10, 0x6e, 0xc1, - 0x7a, 0xfe, 0xd2, 0x6e, 0xe0, 0x34, 0x73, 0x7b, 0xf5, 0xf0, 0xa8, 0x89, 0x60, 0xb9, 0x64, 0x2c, - 0x1b, 0x2a, 0xcf, 0x8c, 0xfb, 0xc6, 0xef, 0x8c, 0xdf, 0x1b, 0xcd, 0xff, 0xe6, 0xa1, 0xf8, 0x8a, - 0x46, 0x23, 0xcf, 0xf9, 0xba, 0x59, 0xf8, 0xf0, 0x44, 0x16, 0x66, 0x06, 0x4b, 0xab, 0x9d, 0x4b, - 0xc4, 0x2d, 0x28, 0xd1, 0xc0, 0x0d, 0x99, 0x17, 0x08, 0x9d, 0x85, 0x99, 0x91, 0xda, 0xd3, 0x18, - 0x3c, 0x45, 0xa3, 0x3d, 0x58, 0x8a, 0x8b, 0xcb, 0x3e, 0x91, 0x82, 0xeb, 0x59, 0xf4, 0xdf, 0x2a, - 0xa0, 0xce, 0x9d, 0xc5, 0x61, 0x6a, 0x87, 0x76, 0x61, 0x29, 0x8c, 0xe8, 0xc8, 0x63, 0x43, 0x6e, - 0x2b, 0x27, 0x0a, 0x97, 0x72, 0x02, 0x2f, 0x26, 0x2c, 0xb9, 0x43, 0xbf, 0x82, 0x45, 0x49, 0xb6, - 0x93, 0xa6, 0x04, 0x17, 0x36, 0x25, 0x5c, 0x91, 0x04, 0xbd, 0x41, 0x2f, 0xe1, 0x3b, 0x27, 0xac, - 0x98, 0x0a, 0xaa, 0x5c, 0x2c, 0xe8, 0x66, 0xda, 0x12, 0xfd, 0x71, 0x1b, 0x1d, 0x1e, 0x35, 0xab, - 0xb0, 0x98, 0x4e, 0x81, 0xe6, 0x9f, 0x73, 0x50, 0x4a, 0x02, 0x89, 0x1e, 0xe8, 0x3b, 0x33, 0xce, - 0x8e, 0x5a, 0x82, 0x55, 0xfe, 0xc6, 0xd7, 0xf5, 0x00, 0x16, 0x42, 0x16, 0x09, 0x6e, 0xe5, 0x54, - 0x72, 0x66, 0xd6, 0xfb, 0x3e, 0x8b, 0xc4, 0x0e, 0x0b, 0xde, 0x79, 0x3d, 0x1c, 0x83, 0xd1, 0x5b, - 0xa8, 0x8c, 0xbc, 0x48, 0x0c, 0x89, 0x6f, 0x7b, 0x21, 0xb7, 0xf2, 0x8a, 0xfb, 0xa3, 0xf3, 0x54, - 0xb6, 0xde, 0xc4, 0xf8, 0xee, 0x7e, 0xa7, 0x3a, 0x39, 0x6e, 0xc0, 0x74, 0xcb, 0x31, 0x68, 0x51, - 0xdd, 0x90, 0xd7, 0x5e, 0x40, 0x79, 0x7a, 0x82, 0xee, 0x01, 0x04, 0x71, 0x5d, 0xd8, 0xd3, 0xcc, - 0x5e, 0x9a, 0x1c, 0x37, 0xca, 0xba, 0x5a, 0xba, 0xbb, 0xb8, 0xac, 0x01, 0x5d, 0x17, 0x21, 0x30, - 0x89, 0xeb, 0x46, 0x2a, 0xcf, 0xcb, 0x58, 0xad, 0x9b, 0x7f, 0x2a, 0x82, 0xf9, 0x9a, 0xf0, 0xfe, - 0x75, 0xb7, 0x68, 0xa9, 0x73, 0xae, 0x32, 0xee, 0x01, 0xf0, 0x38, 0xdf, 0xa4, 0x3b, 0xe6, 0xcc, - 0x1d, 0x9d, 0x85, 0xd2, 0x1d, 0x0d, 0x88, 0xdd, 0xe1, 0x3e, 0x13, 0xaa, 0x08, 0x4c, 0xac, 0xd6, - 0xe8, 0x36, 0x14, 0x03, 0xe6, 0x2a, 0x7a, 0x41, 0xd1, 0x61, 0x72, 0xdc, 0x28, 0xc8, 0xa6, 0xd3, - 0xdd, 0xc5, 0x05, 0x79, 0xd4, 0x75, 0x55, 0xd3, 0x09, 0x02, 0x26, 0x88, 0x6c, 0xe8, 0x5c, 0xf7, - 0xce, 0xcc, 0xec, 0x6f, 0xcf, 0x60, 0x49, 0xbf, 0x4b, 0x31, 0xd1, 0x1b, 0xb8, 0x99, 0xd8, 0x9b, - 0x16, 0x58, 0xba, 0x8a, 0x40, 0xa4, 0x25, 0xa4, 0x4e, 0x52, 0x6f, 0x4c, 0xf9, 0xec, 0x37, 0x46, - 0x45, 0x30, 0xeb, 0x8d, 0xe9, 0xc0, 0x92, 0x4b, 0xb9, 0x17, 0x51, 0x57, 0xb5, 0x09, 0xaa, 0x2a, - 0xb3, 0xba, 0xf9, 0xfd, 0xf3, 0x84, 0x50, 0xbc, 0xa8, 0x39, 0x6a, 0x87, 0xda, 0x50, 0xd2, 0x79, - 0xc3, 0xad, 0xca, 0x55, 0x9a, 0xf2, 0x94, 0x76, 0xa2, 0xcd, 0x2d, 0x5e, 0xa9, 0xcd, 0x3d, 0x04, - 0xf0, 0x59, 0xcf, 0x76, 0x23, 0x6f, 0x44, 0x23, 0x6b, 0x49, 0x4f, 0x1c, 0x19, 0xdc, 0x5d, 0x85, - 0xc0, 0x65, 0x9f, 0xf5, 0xe2, 0xe5, 0x5c, 0x53, 0xaa, 0x5e, 0xb1, 0x29, 0x11, 0xa8, 0x11, 0xce, - 0xbd, 0x5e, 0x40, 0x5d, 0xbb, 0x47, 0x03, 0x1a, 0x79, 0x8e, 0x1d, 0x51, 0xce, 0x86, 0x91, 0x43, - 0xb9, 0xf5, 0x2d, 0x15, 0x89, 0xcc, 0x99, 0xe1, 0x69, 0x0c, 0xc6, 0x1a, 0x8b, 0xad, 0x44, 0xcc, - 0xa9, 0x03, 0xbe, 0x5d, 0x3b, 0x3c, 0x6a, 0xae, 0xc2, 0x4a, 0xba, 0x4d, 0x6d, 0x19, 0x4f, 0x8c, - 0x67, 0xc6, 0xbe, 0xd1, 0xfc, 0x7b, 0x0e, 0xbe, 0x3d, 0x17, 0x53, 0xf4, 0x53, 0x28, 0xea, 0xa8, - 0x9e, 0x37, 0xf9, 0x69, 0x1e, 0x4e, 0xb0, 0x68, 0x0d, 0xca, 0xb2, 0xc4, 0x29, 0xe7, 0x34, 0x6e, - 0x5e, 0x65, 0x3c, 0xfb, 0x80, 0x2c, 0x28, 0x12, 0xdf, 0x23, 0xf2, 0x2c, 0xaf, 0xce, 0x92, 0x2d, - 0x1a, 0xc2, 0x6a, 0x1c, 0x7a, 0x7b, 0xf6, 0xc0, 0xda, 0x2c, 0x14, 0xdc, 0x32, 0x95, 0xff, 0x8f, - 0x2f, 0x95, 0x09, 0xfa, 0x72, 0x66, 0x1f, 0x5e, 0x86, 0x82, 0xef, 0x05, 0x22, 0x1a, 0xe3, 0x15, - 0x37, 0xe3, 0xa8, 0xf6, 0x14, 0x6e, 0x9d, 0x49, 0x41, 0xcb, 0x90, 0xef, 0xd3, 0x71, 0xdc, 0x9e, - 0xb0, 0x5c, 0xa2, 0x15, 0x58, 0x18, 0x11, 0x7f, 0x48, 0x75, 0x37, 0x8b, 0x37, 0xdb, 0xb9, 0x2d, - 0xa3, 0xf9, 0x29, 0x07, 0x45, 0x6d, 0xce, 0x75, 0x3f, 0xf9, 0x5a, 0xed, 0x5c, 0x63, 0x7b, 0x04, - 0x8b, 0x3a, 0xa4, 0x71, 0x45, 0x9a, 0x17, 0xe6, 0x74, 0x25, 0xc6, 0xc7, 0xd5, 0xf8, 0x08, 0x4c, - 0x2f, 0x24, 0x03, 0xfd, 0xdc, 0x67, 0x6a, 0xee, 0xee, 0xb7, 0x5f, 0xbc, 0x0c, 0xe3, 0xc6, 0x52, - 0x9a, 0x1c, 0x37, 0x4c, 0xf9, 0x01, 0x2b, 0x5a, 0xe6, 0xc3, 0xf8, 0x97, 0x05, 0x28, 0xee, 0xf8, - 0x43, 0x2e, 0x68, 0x74, 0xdd, 0x41, 0xd2, 0x6a, 0xe7, 0x82, 0xb4, 0x03, 0xc5, 0x88, 0x31, 0x61, - 0x3b, 0xe4, 0xbc, 0xf8, 0x60, 0xc6, 0xc4, 0x4e, 0xbb, 0x53, 0x95, 0x44, 0xd9, 0xdb, 0xe3, 0x3d, - 0x2e, 0x48, 0xea, 0x0e, 0x41, 0x6f, 0x61, 0x35, 0x79, 0x11, 0x0f, 0x18, 0x13, 0x5c, 0x44, 0x24, - 0xb4, 0xfb, 0x74, 0x2c, 0x67, 0xa5, 0xfc, 0x59, 0x83, 0xf6, 0x5e, 0xe0, 0x44, 0x63, 0x15, 0xbc, - 0xe7, 0x74, 0x8c, 0x57, 0xb4, 0x80, 0x4e, 0xc2, 0x7f, 0x4e, 0xc7, 0x1c, 0x3d, 0x86, 0x35, 0x3a, - 0x85, 0x49, 0x89, 0xb6, 0x4f, 0x06, 0xf2, 0xad, 0xb7, 0x1d, 0x9f, 0x39, 0x7d, 0xf5, 0xdc, 0x98, - 0xf8, 0x16, 0x4d, 0x8b, 0xfa, 0x75, 0x8c, 0xd8, 0x91, 0x00, 0xc4, 0xc1, 0x3a, 0xf0, 0x89, 0xd3, - 0xf7, 0x3d, 0x2e, 0xff, 0x97, 0x4a, 0xcd, 0xcd, 0xf2, 0xc5, 0x90, 0xb6, 0x6d, 0x9d, 0x13, 0xad, - 0x56, 0x67, 0xc6, 0x4d, 0x4d, 0xe1, 0xba, 0xa2, 0xbe, 0x7b, 0x90, 0x7d, 0x8a, 0x3a, 0x50, 0x19, - 0x06, 0x52, 0x7d, 0x1c, 0x83, 0xf2, 0x65, 0x63, 0x00, 0x31, 0x4b, 0x7a, 0x5e, 0x1b, 0xc1, 0xda, - 0x79, 0xca, 0x33, 0x6a, 0xf3, 0x49, 0xba, 0x36, 0x2b, 0x9b, 0x77, 0xb3, 0xf4, 0x65, 0x8b, 0x4c, - 0xd5, 0x71, 0x66, 0xda, 0xfe, 0xcd, 0x80, 0xc2, 0x2b, 0xea, 0x44, 0x54, 0x7c, 0xd5, 0xac, 0xdd, - 0x3a, 0x91, 0xb5, 0xf5, 0xec, 0x41, 0x58, 0x6a, 0x9d, 0x4b, 0xda, 0x1a, 0x94, 0xbc, 0x40, 0xd0, - 0x28, 0x20, 0xbe, 0xca, 0xda, 0x12, 0x9e, 0xee, 0x33, 0x1d, 0xf8, 0x64, 0x40, 0x21, 0x9e, 0x14, - 0xaf, 0xdb, 0x81, 0x58, 0xeb, 0x69, 0x07, 0x32, 0x8d, 0xfc, 0x8f, 0x01, 0xa5, 0xe4, 0xc1, 0xfa, - 0xaa, 0x66, 0x9e, 0x9a, 0xbc, 0xf2, 0xff, 0xf7, 0xe4, 0x85, 0xc0, 0xec, 0x7b, 0x81, 0x9e, 0x11, - 0xb1, 0x5a, 0xa3, 0x16, 0x14, 0x43, 0x32, 0xf6, 0x19, 0x71, 0x75, 0xa3, 0x5c, 0x99, 0xfb, 0x95, - 0xa2, 0x1d, 0x8c, 0x71, 0x02, 0xda, 0x5e, 0x39, 0x3c, 0x6a, 0x2e, 0x43, 0x35, 0xed, 0xf9, 0x7b, - 0xa3, 0xf9, 0x4f, 0x03, 0xca, 0x7b, 0x7f, 0x14, 0x34, 0x50, 0xf3, 0xc0, 0x37, 0xd2, 0xf9, 0xf5, - 0xf9, 0x5f, 0x32, 0xca, 0x27, 0x7e, 0xa4, 0xc8, 0xba, 0xd4, 0x8e, 0xf5, 0xf9, 0x4b, 0xfd, 0xc6, - 0xbf, 0xbe, 0xd4, 0x6f, 0x7c, 0x9c, 0xd4, 0x8d, 0xcf, 0x93, 0xba, 0xf1, 0x8f, 0x49, 0xdd, 0xf8, - 0xf7, 0xa4, 0x6e, 0x1c, 0x14, 0x54, 0x7c, 0x7e, 0xf2, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf9, - 0xa1, 0x26, 0x54, 0x90, 0x13, 0x00, 0x00, + // 1544 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x73, 0xdb, 0x4c, + 0x1d, 0xaf, 0x6c, 0xc5, 0x2f, 0x7f, 0x27, 0x26, 0xec, 0x13, 0x82, 0x6a, 0x82, 0x1d, 0xfc, 0x0c, + 0xcc, 0x33, 0xcf, 0x74, 0x9c, 0x12, 0x0a, 0xa4, 0x81, 0xd2, 0xda, 0x49, 0x68, 0x3d, 0xa5, 0x34, + 0xb3, 0x29, 0x2d, 0x37, 0xb1, 0x91, 0x36, 0xae, 0xb0, 0xac, 0xd5, 0x68, 0xd7, 0x2e, 0xbe, 0xf5, + 0x1c, 0x3e, 0x40, 0x6e, 0x1c, 0xfa, 0x2d, 0xb8, 0x70, 0xe0, 0xd4, 0x23, 0xc3, 0x81, 0xe1, 0x94, + 0xa1, 0xfe, 0x16, 0xcc, 0x70, 0x60, 0x76, 0xb5, 0xb2, 0x95, 0x58, 0x79, 0x63, 0x3a, 0x19, 0x4e, + 0xd1, 0x6a, 0x7f, 0xbf, 0xff, 0x9b, 0xfe, 0x6f, 0x31, 0xdc, 0xeb, 0x79, 0xe2, 0xed, 0xf0, 0xb0, + 0xe5, 0xb0, 0xc1, 0x86, 0xcb, 0x9c, 0x3e, 0x8d, 0x36, 0xf8, 0x3b, 0x12, 0x0d, 0xfa, 0x9e, 0xd8, + 0x20, 0xa1, 0xb7, 0xc1, 0x0e, 0x7f, 0x4f, 0x1d, 0xc1, 0x5b, 0x61, 0xc4, 0x04, 0x43, 0x28, 0x86, + 0xb4, 0x12, 0x48, 0x6b, 0xf4, 0xc3, 0xda, 0xd7, 0x57, 0x48, 0x10, 0xe3, 0x90, 0x6a, 0xfe, 0x95, + 0x58, 0x1e, 0x52, 0x27, 0xc1, 0x36, 0x7a, 0x8c, 0xf5, 0x7c, 0xba, 0xa1, 0x4e, 0x87, 0xc3, 0xa3, + 0x0d, 0xe1, 0x0d, 0x28, 0x17, 0x64, 0x10, 0x6a, 0xc0, 0x4a, 0x8f, 0xf5, 0x98, 0x7a, 0xdc, 0x90, + 0x4f, 0xfa, 0xed, 0xdd, 0xf3, 0x34, 0x12, 0x8c, 0xf5, 0xd5, 0x4f, 0x2f, 0xd1, 0x3e, 0x85, 0x87, + 0xfe, 0xb0, 0xe7, 0x05, 0xfa, 0x4f, 0x4c, 0x6c, 0xfe, 0xd9, 0x00, 0xf3, 0x05, 0x15, 0x04, 0xfd, + 0x0c, 0x8a, 0x23, 0x1a, 0x71, 0x8f, 0x05, 0x96, 0xb1, 0x6e, 0x7c, 0x55, 0xd9, 0xfc, 0x4e, 0x6b, + 0x3e, 0x22, 0xad, 0xd7, 0x31, 0xa4, 0x63, 0x7e, 0x3c, 0x6d, 0xdc, 0xc1, 0x09, 0x03, 0x3d, 0x04, + 0x70, 0x22, 0x4a, 0x04, 0x75, 0x6d, 0x22, 0xac, 0x9c, 0xe2, 0xd7, 0x5a, 0xb1, 0xb9, 0xad, 0x44, + 0x7f, 0xeb, 0x55, 0xe2, 0x25, 0x2e, 0x6b, 0x74, 0x5b, 0x48, 0xea, 0x30, 0x74, 0x13, 0x6a, 0xfe, + 0x6a, 0xaa, 0x46, 0xb7, 0x45, 0xf3, 0xfd, 0x02, 0x98, 0xbf, 0x66, 0x2e, 0x45, 0xab, 0x90, 0xf3, + 0x5c, 0x65, 0x76, 0xb9, 0x53, 0x98, 0x9c, 0x36, 0x72, 0xdd, 0x5d, 0x9c, 0xf3, 0x5c, 0xb4, 0x09, + 0xe6, 0x80, 0x0a, 0xa2, 0x0d, 0xb2, 0xb2, 0x1c, 0x92, 0xbe, 0x6b, 0x6f, 0x14, 0x16, 0xfd, 0x04, + 0x4c, 0xf9, 0xa9, 0xb4, 0x25, 0x6b, 0x59, 0x1c, 0xa9, 0xf3, 0x20, 0xa4, 0x4e, 0xc2, 0x93, 0x78, + 0xb4, 0x07, 0x15, 0x97, 0x72, 0x27, 0xf2, 0x42, 0x21, 0x63, 0x68, 0x2a, 0xfa, 0x97, 0x17, 0xd1, + 0x77, 0x67, 0x50, 0x9c, 0xe6, 0xa1, 0x9f, 0x43, 0x81, 0x0b, 0x22, 0x86, 0xdc, 0x5a, 0x50, 0x12, + 0xea, 0x17, 0x1a, 0xa0, 0x50, 0xda, 0x04, 0xcd, 0x41, 0xcf, 0xa0, 0x3a, 0x20, 0x01, 0xe9, 0xd1, + 0xc8, 0xd6, 0x52, 0x0a, 0x4a, 0xca, 0xf7, 0x32, 0x5d, 0x8f, 0x91, 0xb1, 0x20, 0xbc, 0x34, 0x48, + 0x1f, 0x51, 0x17, 0x80, 0x08, 0x41, 0x9c, 0xb7, 0x03, 0x1a, 0x08, 0xab, 0xa8, 0xa4, 0x7c, 0x3f, + 0xd3, 0x16, 0x2a, 0xde, 0xb1, 0xa8, 0xdf, 0x9e, 0x82, 0x3b, 0x39, 0xcb, 0xc0, 0x29, 0x32, 0x7a, + 0x0a, 0x15, 0x87, 0x46, 0xc2, 0x3b, 0xf2, 0x1c, 0x22, 0xa8, 0x55, 0x52, 0xb2, 0x1a, 0x59, 0xb2, + 0x76, 0x66, 0x30, 0xed, 0x58, 0x9a, 0x89, 0xee, 0x83, 0x19, 0x31, 0x9f, 0x5a, 0xe5, 0x75, 0xe3, + 0xab, 0xea, 0xc5, 0x9f, 0x06, 0x33, 0x9f, 0x62, 0x85, 0x94, 0xaa, 0x67, 0x86, 0x70, 0x0b, 0xd6, + 0xf3, 0xd7, 0x76, 0x03, 0xa7, 0x99, 0xdb, 0xab, 0xc7, 0x27, 0x4d, 0x04, 0xcb, 0x25, 0x63, 0xd9, + 0x50, 0x79, 0x66, 0xdc, 0x37, 0x7e, 0x6b, 0xfc, 0xce, 0x68, 0xfe, 0x27, 0x0f, 0xc5, 0x03, 0x1a, + 0x8d, 0x3c, 0xe7, 0xf3, 0x66, 0xe1, 0xc3, 0x33, 0x59, 0x98, 0x19, 0x2c, 0xad, 0x76, 0x2e, 0x11, + 0xb7, 0xa0, 0x44, 0x03, 0x37, 0x64, 0x5e, 0x20, 0x74, 0x16, 0x66, 0x46, 0x6a, 0x4f, 0x63, 0xf0, + 0x14, 0x8d, 0xf6, 0x60, 0x29, 0x2e, 0x2e, 0xfb, 0x4c, 0x0a, 0xae, 0x67, 0xd1, 0x7f, 0xa3, 0x80, + 0x3a, 0x77, 0x16, 0x87, 0xa9, 0x13, 0xda, 0x85, 0xa5, 0x30, 0xa2, 0x23, 0x8f, 0x0d, 0xb9, 0xad, + 0x9c, 0x28, 0x5c, 0xcb, 0x09, 0xbc, 0x98, 0xb0, 0xe4, 0x09, 0xfd, 0x02, 0x16, 0x25, 0xd9, 0x4e, + 0x9a, 0x12, 0x5c, 0xd9, 0x94, 0x70, 0x45, 0x12, 0xf4, 0x01, 0xbd, 0x84, 0x6f, 0x9d, 0xb1, 0x62, + 0x2a, 0xa8, 0x72, 0xb5, 0xa0, 0x2f, 0xd2, 0x96, 0xe8, 0x97, 0xdb, 0xe8, 0xf8, 0xa4, 0x59, 0x85, + 0xc5, 0x74, 0x0a, 0x34, 0xff, 0x94, 0x83, 0x52, 0x12, 0x48, 0xf4, 0x40, 0x7f, 0x33, 0xe3, 0xe2, + 0xa8, 0x25, 0x58, 0xe5, 0x6f, 0xfc, 0xb9, 0x1e, 0xc0, 0x42, 0xc8, 0x22, 0xc1, 0xad, 0x9c, 0x4a, + 0xce, 0xcc, 0x7a, 0xdf, 0x67, 0x91, 0xd8, 0x61, 0xc1, 0x91, 0xd7, 0xc3, 0x31, 0x18, 0xbd, 0x81, + 0xca, 0xc8, 0x8b, 0xc4, 0x90, 0xf8, 0xb6, 0x17, 0x72, 0x2b, 0xaf, 0xb8, 0x3f, 0xb8, 0x4c, 0x65, + 0xeb, 0x75, 0x8c, 0xef, 0xee, 0x77, 0xaa, 0x93, 0xd3, 0x06, 0x4c, 0x8f, 0x1c, 0x83, 0x16, 0xd5, + 0x0d, 0x79, 0xed, 0x05, 0x94, 0xa7, 0x37, 0xe8, 0x1e, 0x40, 0x10, 0xd7, 0x85, 0x3d, 0xcd, 0xec, + 0xa5, 0xc9, 0x69, 0xa3, 0xac, 0xab, 0xa5, 0xbb, 0x8b, 0xcb, 0x1a, 0xd0, 0x75, 0x11, 0x02, 0x93, + 0xb8, 0x6e, 0xa4, 0xf2, 0xbc, 0x8c, 0xd5, 0x73, 0xf3, 0x8f, 0x45, 0x30, 0x5f, 0x11, 0xde, 0xbf, + 0xed, 0x16, 0x2d, 0x75, 0xce, 0x55, 0xc6, 0x3d, 0x00, 0x1e, 0xe7, 0x9b, 0x74, 0xc7, 0x9c, 0xb9, + 0xa3, 0xb3, 0x50, 0xba, 0xa3, 0x01, 0xb1, 0x3b, 0xdc, 0x67, 0x42, 0x15, 0x81, 0x89, 0xd5, 0x33, + 0xfa, 0x12, 0x8a, 0x01, 0x73, 0x15, 0xbd, 0xa0, 0xe8, 0x30, 0x39, 0x6d, 0x14, 0x64, 0xd3, 0xe9, + 0xee, 0xe2, 0x82, 0xbc, 0xea, 0xba, 0xaa, 0xe9, 0x04, 0x01, 0x13, 0x44, 0x36, 0x74, 0xae, 0x7b, + 0x67, 0x66, 0xf6, 0xb7, 0x67, 0xb0, 0xa4, 0xdf, 0xa5, 0x98, 0xe8, 0x35, 0x7c, 0x91, 0xd8, 0x9b, + 0x16, 0x58, 0xba, 0x89, 0x40, 0xa4, 0x25, 0xa4, 0x6e, 0x52, 0x33, 0xa6, 0x7c, 0xf1, 0x8c, 0x51, + 0x11, 0xcc, 0x9a, 0x31, 0x1d, 0x58, 0x72, 0x29, 0xf7, 0x22, 0xea, 0xaa, 0x36, 0x41, 0x55, 0x65, + 0x56, 0x37, 0xbf, 0x7b, 0x99, 0x10, 0x8a, 0x17, 0x35, 0x47, 0x9d, 0x50, 0x1b, 0x4a, 0x3a, 0x6f, + 0xb8, 0x55, 0xb9, 0x49, 0x53, 0x9e, 0xd2, 0xce, 0xb4, 0xb9, 0xc5, 0x1b, 0xb5, 0xb9, 0x87, 0x00, + 0x3e, 0xeb, 0xd9, 0x6e, 0xe4, 0x8d, 0x68, 0x64, 0x2d, 0xe9, 0x8d, 0x23, 0x83, 0xbb, 0xab, 0x10, + 0xb8, 0xec, 0xb3, 0x5e, 0xfc, 0x38, 0xd7, 0x94, 0xaa, 0x37, 0x6c, 0x4a, 0x04, 0x6a, 0x84, 0x73, + 0xaf, 0x17, 0x50, 0xd7, 0xee, 0xd1, 0x80, 0x46, 0x9e, 0x63, 0x47, 0x94, 0xb3, 0x61, 0xe4, 0x50, + 0x6e, 0x7d, 0x43, 0x45, 0x22, 0x73, 0x67, 0x78, 0x1a, 0x83, 0xb1, 0xc6, 0x62, 0x2b, 0x11, 0x73, + 0xee, 0x82, 0x6f, 0xd7, 0x8e, 0x4f, 0x9a, 0xab, 0xb0, 0x92, 0x6e, 0x53, 0x5b, 0xc6, 0x13, 0xe3, + 0x99, 0xb1, 0x6f, 0x34, 0xff, 0x9a, 0x83, 0x6f, 0xce, 0xc5, 0x14, 0xfd, 0x18, 0x8a, 0x3a, 0xaa, + 0x97, 0x6d, 0x7e, 0x9a, 0x87, 0x13, 0x2c, 0x5a, 0x83, 0xb2, 0x2c, 0x71, 0xca, 0x39, 0x8d, 0x9b, + 0x57, 0x19, 0xcf, 0x5e, 0x20, 0x0b, 0x8a, 0xc4, 0xf7, 0x88, 0xbc, 0xcb, 0xab, 0xbb, 0xe4, 0x88, + 0x86, 0xb0, 0x1a, 0x87, 0xde, 0x9e, 0x0d, 0x58, 0x9b, 0x85, 0x82, 0x5b, 0xa6, 0xf2, 0xff, 0xf1, + 0xb5, 0x32, 0x41, 0x7f, 0x9c, 0xd9, 0x8b, 0x97, 0xa1, 0xe0, 0x7b, 0x81, 0x88, 0xc6, 0x78, 0xc5, + 0xcd, 0xb8, 0xaa, 0x3d, 0x85, 0xbb, 0x17, 0x52, 0xd0, 0x32, 0xe4, 0xfb, 0x74, 0x1c, 0xb7, 0x27, + 0x2c, 0x1f, 0xd1, 0x0a, 0x2c, 0x8c, 0x88, 0x3f, 0xa4, 0xba, 0x9b, 0xc5, 0x87, 0xed, 0xdc, 0x96, + 0xd1, 0xfc, 0x90, 0x83, 0xa2, 0x36, 0xe7, 0xb6, 0x47, 0xbe, 0x56, 0x3b, 0xd7, 0xd8, 0x1e, 0xc1, + 0xa2, 0x0e, 0x69, 0x5c, 0x91, 0xe6, 0x95, 0x39, 0x5d, 0x89, 0xf1, 0x71, 0x35, 0x3e, 0x02, 0xd3, + 0x0b, 0xc9, 0x40, 0x8f, 0xfb, 0x4c, 0xcd, 0xdd, 0xfd, 0xf6, 0x8b, 0x97, 0x61, 0xdc, 0x58, 0x4a, + 0x93, 0xd3, 0x86, 0x29, 0x5f, 0x60, 0x45, 0xcb, 0x1c, 0x8c, 0x7f, 0x5f, 0x80, 0xe2, 0x8e, 0x3f, + 0xe4, 0x82, 0x46, 0xb7, 0x1d, 0x24, 0xad, 0x76, 0x2e, 0x48, 0x3b, 0x50, 0x8c, 0x18, 0x13, 0xb6, + 0x43, 0x2e, 0x8b, 0x0f, 0x66, 0x4c, 0xec, 0xb4, 0x3b, 0x55, 0x49, 0x94, 0xbd, 0x3d, 0x3e, 0xe3, + 0x82, 0xa4, 0xee, 0x10, 0xf4, 0x06, 0x56, 0x93, 0x89, 0x78, 0xc8, 0x98, 0xe0, 0x22, 0x22, 0xa1, + 0xdd, 0xa7, 0x63, 0xb9, 0x2b, 0xe5, 0x2f, 0x5a, 0xb4, 0xf7, 0x02, 0x27, 0x1a, 0xab, 0xe0, 0x3d, + 0xa7, 0x63, 0xbc, 0xa2, 0x05, 0x74, 0x12, 0xfe, 0x73, 0x3a, 0xe6, 0xe8, 0x31, 0xac, 0xd1, 0x29, + 0x4c, 0x4a, 0xb4, 0x7d, 0x32, 0x90, 0xb3, 0xde, 0x76, 0x7c, 0xe6, 0xf4, 0xd5, 0xb8, 0x31, 0xf1, + 0x5d, 0x9a, 0x16, 0xf5, 0xab, 0x18, 0xb1, 0x23, 0x01, 0x88, 0x83, 0x75, 0xe8, 0x13, 0xa7, 0xef, + 0x7b, 0x5c, 0xfe, 0x2f, 0x95, 0xda, 0x9b, 0xe5, 0xc4, 0x90, 0xb6, 0x6d, 0x5d, 0x12, 0xad, 0x56, + 0x67, 0xc6, 0x4d, 0x6d, 0xe1, 0xba, 0xa2, 0xbe, 0x7d, 0x98, 0x7d, 0x8b, 0x3a, 0x50, 0x19, 0x06, + 0x52, 0x7d, 0x1c, 0x83, 0xf2, 0x75, 0x63, 0x00, 0x31, 0x4b, 0x79, 0xbe, 0x06, 0xe6, 0x91, 0xdc, + 0x61, 0xe4, 0x18, 0x29, 0xc5, 0xc9, 0xf5, 0xcb, 0xee, 0xfe, 0x01, 0x56, 0x6f, 0x6b, 0x23, 0x58, + 0xbb, 0xcc, 0xb4, 0x8c, 0xca, 0x7d, 0x92, 0xae, 0xdc, 0xca, 0xe6, 0xd7, 0x59, 0xd6, 0x64, 0x8b, + 0x4c, 0x55, 0x79, 0x66, 0x52, 0xff, 0xc5, 0x80, 0xc2, 0x01, 0x75, 0x22, 0x2a, 0x3e, 0x6b, 0x4e, + 0x6f, 0x9d, 0xc9, 0xe9, 0x7a, 0xf6, 0x9a, 0x2c, 0xb5, 0xce, 0xa5, 0x74, 0x0d, 0x4a, 0x5e, 0x20, + 0x68, 0x14, 0x10, 0x5f, 0xe5, 0x74, 0x09, 0x4f, 0xcf, 0x99, 0x0e, 0x7c, 0x30, 0xa0, 0x10, 0xef, + 0x91, 0xb7, 0xed, 0x40, 0xac, 0xf5, 0xbc, 0x03, 0x99, 0x46, 0xfe, 0xdb, 0x80, 0x52, 0x32, 0xce, + 0x3e, 0xab, 0x99, 0xe7, 0xf6, 0xb2, 0xfc, 0xff, 0xbc, 0x97, 0x21, 0x30, 0xfb, 0x5e, 0xa0, 0x37, + 0x48, 0xac, 0x9e, 0x51, 0x0b, 0x8a, 0x21, 0x19, 0xfb, 0x8c, 0xb8, 0xba, 0x8d, 0xae, 0xcc, 0xfd, + 0x86, 0xd1, 0x0e, 0xc6, 0x38, 0x01, 0x6d, 0xaf, 0x1c, 0x9f, 0x34, 0x97, 0xa1, 0x9a, 0xf6, 0xfc, + 0xad, 0xd1, 0xfc, 0x87, 0x01, 0xe5, 0xbd, 0x3f, 0x08, 0x1a, 0xa8, 0x6d, 0xe1, 0xff, 0xd2, 0xf9, + 0xf5, 0xf9, 0xdf, 0x39, 0xca, 0x67, 0x7e, 0xc2, 0xc8, 0xfa, 0xa8, 0x1d, 0xeb, 0xe3, 0xa7, 0xfa, + 0x9d, 0x7f, 0x7e, 0xaa, 0xdf, 0x79, 0x3f, 0xa9, 0x1b, 0x1f, 0x27, 0x75, 0xe3, 0x6f, 0x93, 0xba, + 0xf1, 0xaf, 0x49, 0xdd, 0x38, 0x2c, 0xa8, 0xf8, 0xfc, 0xe8, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x07, 0xc5, 0x5a, 0x5b, 0xae, 0x13, 0x00, 0x00, } diff --git a/api/objects.proto b/api/objects.proto index c3ee4195b5..4ebdb26355 100644 --- a/api/objects.proto +++ b/api/objects.proto @@ -336,6 +336,12 @@ message Cluster { // If the key is empty, the node will be unlocked (will not require a key // to start up from a shut down state). repeated EncryptionKey unlock_keys = 9; + + // FIPS specifies whether this cluster should be in FIPS mode. This changes + // the format of the join tokens, and nodes that are not FIPS-enabled should + // reject joining the cluster. Nodes that report themselves to be non-FIPS + // should be rejected from the cluster. + bool fips = 10 [(gogoproto.customname) = "FIPS"]; } // Secret represents a secret that should be passed to a container or a node, diff --git a/manager/manager.go b/manager/manager.go index 879dad035e..677c9ae3be 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -121,6 +121,11 @@ type Config struct { // PluginGetter provides access to docker's plugin inventory. PluginGetter plugingetter.PluginGetter + + // FIPS is a boolean stating whether the node is FIPS enabled - if this is the + // first node in the cluster, this setting is used to set the cluster-wide mandatory + // FIPS setting. + FIPS bool } // Manager is the cluster manager for Swarm. @@ -855,7 +860,8 @@ func (m *Manager) becomeLeader(ctx context.Context) { raftCfg, api.EncryptionConfig{AutoLockManagers: m.config.AutoLockManagers}, unlockKeys, - rootCA)) + rootCA, + m.config.FIPS)) if err != nil && err != store.ErrExist { log.G(ctx).WithError(err).Errorf("error creating cluster object") @@ -1016,7 +1022,8 @@ func defaultClusterObject( raftCfg api.RaftConfig, encryptionConfig api.EncryptionConfig, initialUnlockKeys []*api.EncryptionKey, - rootCA *ca.RootCA) *api.Cluster { + rootCA *ca.RootCA, + fips bool) *api.Cluster { var caKey []byte if rcaSigner, err := rootCA.Signer(); err == nil { caKey = rcaSigner.Key @@ -1048,6 +1055,7 @@ func defaultClusterObject( }, }, UnlockKeys: initialUnlockKeys, + FIPS: fips, } } From 3688812bb18ef33eb5e3f4fc93b9f794994572e3 Mon Sep 17 00:00:00 2001 From: cyli Date: Fri, 16 Mar 2018 17:41:04 -0700 Subject: [PATCH 13/15] If the cluster requires FIPS, the cluster ID and join token will also reflect this property. So all TLS certs will have the cluster ID, which says whether the cluster is FIPS, in the Org field. If a node loads up its TLS cert, sees that that the cluster requires FIPS, and FIPS mode is not enabled on that node, the node will shut down. If a non-FIPS node gets a join token that indicate that the cluster mandates FIPS, it will refuse to join. Signed-off-by: Ying Li (cherry picked from commit 9943770cf2e42aef706b8a1082cba33a911752f9) --- ca/config.go | 118 +++++++++++++++++++--- ca/config_test.go | 94 +++++++++++------ ca/reconciler.go | 4 +- ca/server_test.go | 4 +- ca/testutils/cautils.go | 32 +++++- integration/cluster.go | 98 +++++++----------- integration/integration_test.go | 104 +++++++++++++++++++ manager/controlapi/cluster.go | 4 +- manager/controlapi/cluster_test.go | 4 +- manager/manager.go | 4 +- node/node.go | 45 ++++++++- node/node_test.go | 157 +++++++++++++++++++++++++++++ 12 files changed, 545 insertions(+), 123 deletions(-) diff --git a/ca/config.go b/ca/config.go index 0fd4ebe0f0..4a7230ac2f 100644 --- a/ca/config.go +++ b/ca/config.go @@ -55,6 +55,11 @@ var ( // GetCertRetryInterval is how long to wait before retrying a node // certificate or root certificate request. GetCertRetryInterval = 2 * time.Second + + // errInvalidJoinToken is returned when attempting to parse an invalid join + // token (e.g. when attempting to get the version, fipsness, or the root ca + // digest) + errInvalidJoinToken = errors.New("invalid join token") ) // SecurityConfig is used to represent a node's security configuration. It includes information about @@ -87,6 +92,81 @@ type CertificateUpdate struct { Err error } +// ParsedJoinToken is the data from a join token, once parsed +type ParsedJoinToken struct { + // Version is the version of the join token that is being parsed + Version int + + // RootDigest is the digest of the root CA certificate of the cluster, which + // is always part of the join token so that the root CA can be verified + // upon initial node join + RootDigest digest.Digest + + // Secret is the randomly-generated secret part of the join token - when + // rotating a join token, this is the value that is changed unless some other + // property of the cluster (like the root CA) is changed. + Secret string + + // FIPS indicates whether the join token specifies that the cluster mandates + // that all nodes must have FIPS mode enabled. + FIPS bool +} + +// ParseJoinToken parses a join token. Current format is v2, but this is currently used only if the cluster requires +// mandatory FIPS, in order to facilitate mixed version clusters. +// v1: SWMTKN-1--<16-byte secret in base 36 0-left-padded to 25 chars> +// v2: SWMTKN-2-<0/1 whether its FIPS or not>- +func ParseJoinToken(token string) (*ParsedJoinToken, error) { + split := strings.Split(token, "-") + numParts := len(split) + + // v1 has 4, v2 has 5 + if numParts < 4 || split[0] != "SWMTKN" { + return nil, errInvalidJoinToken + } + + var ( + version int + fips bool + ) + + switch split[1] { + case "1": + if numParts != 4 { + return nil, errInvalidJoinToken + } + version = 1 + case "2": + if numParts != 5 || (split[2] != "0" && split[2] != "1") { + return nil, errInvalidJoinToken + } + version = 2 + fips = split[2] == "1" + default: + return nil, errInvalidJoinToken + } + + secret := split[numParts-1] + rootDigest := split[numParts-2] + if len(rootDigest) != base36DigestLen || len(secret) != maxGeneratedSecretLength { + return nil, errInvalidJoinToken + } + + var digestInt big.Int + digestInt.SetString(rootDigest, joinTokenBase) + + d, err := digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16))) + if err != nil { + return nil, err + } + return &ParsedJoinToken{ + Version: version, + RootDigest: d, + Secret: secret, + FIPS: fips, + }, nil +} + func validateRootCAAndTLSCert(rootCA *RootCA, tlsKeyPair *tls.Certificate) error { var ( leafCert *x509.Certificate @@ -275,8 +355,14 @@ func NewConfigPaths(baseCertDir string) *SecurityConfigPaths { } } -// GenerateJoinToken creates a new join token. -func GenerateJoinToken(rootCA *RootCA) string { +// GenerateJoinToken creates a new join token. Current format is v2, but this is +// currently used only if the cluster requires mandatory FIPS, in order to +// facilitate mixed version clusters (the `fips` parameter is set to true). +// Otherwise, v1 is used so as to maintain compatibility in mixed version +// non-FIPS clusters. +// v1: SWMTKN-1--<16-byte secret in base 36 0-left-padded to 25 chars> +// v2: SWMTKN-2-<0/1 whether its FIPS or not>- +func GenerateJoinToken(rootCA *RootCA, fips bool) string { var secretBytes [generatedSecretEntropyBytes]byte if _, err := cryptorand.Read(secretBytes[:]); err != nil { @@ -286,19 +372,13 @@ func GenerateJoinToken(rootCA *RootCA) string { var nn, digest big.Int nn.SetBytes(secretBytes[:]) digest.SetString(rootCA.Digest.Hex(), 16) - return fmt.Sprintf("SWMTKN-1-%0[1]*s-%0[3]*s", base36DigestLen, digest.Text(joinTokenBase), maxGeneratedSecretLength, nn.Text(joinTokenBase)) -} -func getCAHashFromToken(token string) (digest.Digest, error) { - split := strings.Split(token, "-") - if len(split) != 4 || split[0] != "SWMTKN" || split[1] != "1" || len(split[2]) != base36DigestLen || len(split[3]) != maxGeneratedSecretLength { - return "", errors.New("invalid join token") + fmtString := "SWMTKN-1-%0[1]*s-%0[3]*s" + if fips { + fmtString = "SWMTKN-2-1-%0[1]*s-%0[3]*s" } - - var digestInt big.Int - digestInt.SetString(split[2], joinTokenBase) - - return digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16))) + return fmt.Sprintf(fmtString, base36DigestLen, + digest.Text(joinTokenBase), maxGeneratedSecretLength, nn.Text(joinTokenBase)) } // DownloadRootCA tries to retrieve a remote root CA and matches the digest against the provided token. @@ -312,10 +392,11 @@ func DownloadRootCA(ctx context.Context, paths CertPaths, token string, connBrok err error ) if token != "" { - d, err = getCAHashFromToken(token) + parsed, err := ParseJoinToken(token) if err != nil { return RootCA{}, err } + d = parsed.RootDigest } // Get the remote CA certificate, verify integrity with the // hash provided. Retry up to 5 times, in case the manager we @@ -414,6 +495,10 @@ type CertificateRequestConfig struct { NodeCertificateStatusRequestTimeout time.Duration // RetryInterval specifies how long to delay between retries, if non-zero. RetryInterval time.Duration + // Organization is the organization to use for a TLS certificate when creating + // a security config from scratch. If not provided, a random ID is generated. + // For swarm certificates, the organization is the cluster ID. + Organization string } // CreateSecurityConfig creates a new key and cert for this node, either locally @@ -423,7 +508,10 @@ func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWrite // Create a new random ID for this certificate cn := identity.NewID() - org := identity.NewID() + org := config.Organization + if config.Organization == "" { + org = identity.NewID() + } proposedRole := ManagerRole tlsKeyPair, issuerInfo, err := rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org) diff --git a/ca/config_test.go b/ca/config_test.go index 7b9bdab53c..f30d7e3284 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -33,13 +33,33 @@ import ( ) func TestDownloadRootCASuccess(t *testing.T) { - tc := cautils.NewTestCA(t) + for _, fips := range []bool{true, false} { + testDownloadRootCASuccess(t, fips) + } +} +func testDownloadRootCASuccess(t *testing.T, fips bool) { + var tc *cautils.TestCA + if fips { + tc = cautils.NewFIPSTestCA(t) + } else { + tc = cautils.NewTestCA(t) + } defer tc.Stop() + token := ca.GenerateJoinToken(&tc.RootCA, fips) + + // if we require mandatory FIPS, the join token uses a new format. otherwise + // the join token should use the old format. + prefix := "SWMTKN-1-" + if fips { + prefix = "SWMTKN-2-1-" + } + require.True(t, strings.HasPrefix(token, prefix)) + // Remove the CA cert os.RemoveAll(tc.Paths.RootCA.Cert) - rootCA, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, tc.WorkerToken, tc.ConnBroker) + rootCA, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, token, tc.ConnBroker) require.NoError(t, err) require.NotNil(t, rootCA.Pool) require.NotNil(t, rootCA.Certs) @@ -70,23 +90,24 @@ func TestDownloadRootCAWrongCAHash(t *testing.T) { // invalid token for _, invalid := range []string{ "invalidtoken", // completely invalid - "SWMTKN-1-3wkodtpeoipd1u1hi0ykdcdwhw16dk73ulqqtn14b3indz68rf-4myj5xihyto11dg1cn55w8p6", // mistyped + "SWMTKN-1-3wkodtpeoipd1u1hi0ykdcdwhw16dk73ulqqtn14b3indz68rf-4myj5xihyto11dg1cn55w8p6", // mistyped + "SWMTKN-2-1fhvpatk6ms36i3uc64tsv1ybyuxkb899zbjpq4ib64qwbibz4-1g3as27iwmko5yqh1byv868hx", // version 2 should have 5 tokens + "SWMTKN-0-1fhvpatk6ms36i3uc64tsv1ybyuxkb899zbjpq4ib64qwbibz4-1g3as27iwmko5yqh1byv868hx", // invalid version } { _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, invalid, tc.ConnBroker) require.Error(t, err) require.Contains(t, err.Error(), "invalid join token") } - // invalid hash token - splitToken := strings.Split(tc.ManagerToken, "-") - splitToken[2] = "1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e" - replacementToken := strings.Join(splitToken, "-") - - os.RemoveAll(tc.Paths.RootCA.Cert) - - _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, replacementToken, tc.ConnBroker) - require.Error(t, err) - require.Contains(t, err.Error(), "remote CA does not match fingerprint.") + // invalid hash token - can get the wrong hash from both version 1 and version 2 + for _, wrongToken := range []string{ + "SWMTKN-1-1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e-4myj5xihyto11dg1cn55w8p61", + "SWMTKN-2-0-1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e-4myj5xihyto11dg1cn55w8p61", + } { + _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, wrongToken, tc.ConnBroker) + require.Error(t, err) + require.Contains(t, err.Error(), "remote CA does not match fingerprint.") + } } func TestCreateSecurityConfigEmptyDir(t *testing.T) { @@ -98,27 +119,36 @@ func TestCreateSecurityConfigEmptyDir(t *testing.T) { assert.NoError(t, tc.CAServer.Stop()) // Remove all the contents from the temp dir and try again with a new node - os.RemoveAll(tc.TempDir) - krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) - nodeConfig, cancel, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - Token: tc.WorkerToken, - ConnBroker: tc.ConnBroker, - }) - assert.NoError(t, err) - cancel() - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + for _, org := range []string{ + "", + "my_org", + } { + os.RemoveAll(tc.TempDir) + krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) + nodeConfig, cancel, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + ConnBroker: tc.ConnBroker, + Organization: org, + }) + assert.NoError(t, err) + cancel() + assert.NotNil(t, nodeConfig) + assert.NotNil(t, nodeConfig.ClientTLSCreds) + assert.NotNil(t, nodeConfig.ServerTLSCreds) + assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + if org != "" { + assert.Equal(t, org, nodeConfig.ClientTLSCreds.Organization()) + } - root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) - assert.NoError(t, err) + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) - issuerInfo := nodeConfig.IssuerInfo() - assert.NotNil(t, issuerInfo) - assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) - assert.Equal(t, root.RawSubject, issuerInfo.Subject) + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) + } } func TestCreateSecurityConfigNoCerts(t *testing.T) { diff --git a/ca/reconciler.go b/ca/reconciler.go index 6daec76d39..d906475d9b 100644 --- a/ca/reconciler.go +++ b/ca/reconciler.go @@ -229,8 +229,8 @@ func (r *rootRotationReconciler) finishRootRotation(tx store.Tx, expectedRootCA CAKey: cluster.RootCA.RootRotation.CAKey, CACertHash: updatedRootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: GenerateJoinToken(&updatedRootCA), - Manager: GenerateJoinToken(&updatedRootCA), + Worker: GenerateJoinToken(&updatedRootCA, cluster.FIPS), + Manager: GenerateJoinToken(&updatedRootCA, cluster.FIPS), }, LastForcedRotation: cluster.RootCA.LastForcedRotation, } diff --git a/ca/server_test.go b/ca/server_test.go index 7b0b0da12c..996051960d 100644 --- a/ca/server_test.go +++ b/ca/server_test.go @@ -318,9 +318,9 @@ func TestNewNodeCertificateRequiresToken(t *testing.T) { ) assert.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { clusters, _ := store.FindClusters(tx, store.ByName(store.DefaultClusterName)) - newWorkerToken = ca.GenerateJoinToken(&tc.RootCA) + newWorkerToken = ca.GenerateJoinToken(&tc.RootCA, false) clusters[0].RootCA.JoinTokens.Worker = newWorkerToken - newManagerToken = ca.GenerateJoinToken(&tc.RootCA) + newManagerToken = ca.GenerateJoinToken(&tc.RootCA, false) clusters[0].RootCA.JoinTokens.Manager = newManagerToken return store.UpdateCluster(tx, clusters[0]) })) diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index 2171d7f234..c3b086ed76 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "strings" "testing" "time" @@ -111,17 +112,40 @@ func NewTestCA(t *testing.T, krwGenerators ...func(ca.CertPaths) *ca.KeyReadWrit CAKey: key, } - return NewTestCAFromAPIRootCA(t, tempdir, apiRootCA, krwGenerators) + return newTestCA(t, tempdir, apiRootCA, krwGenerators, false) +} + +// NewFIPSTestCA is a helper method that creates a mandatory fips TestCA and a bunch of default +// connections and security configs. +func NewFIPSTestCA(t *testing.T) *TestCA { + tempdir, err := ioutil.TempDir("", "swarm-ca-test-") + require.NoError(t, err) + + cert, key, err := CreateRootCertAndKey("swarm-test-CA") + require.NoError(t, err) + apiRootCA := api.RootCA{ + CACert: cert, + CAKey: key, + } + + return newTestCA(t, tempdir, apiRootCA, nil, true) } // NewTestCAFromAPIRootCA is a helper method that creates a TestCA and a bunch of default // connections and security configs, given a temp directory and an api.RootCA to use for creating // a cluster and for signing. func NewTestCAFromAPIRootCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter) *TestCA { + return newTestCA(t, tempBaseDir, apiRootCA, krwGenerators, false) +} + +func newTestCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter, fips bool) *TestCA { s := store.NewMemoryStore(&stateutils.MockProposer{}) paths := ca.NewConfigPaths(tempBaseDir) organization := identity.NewID() + if fips { + organization = "FIPS." + organization + } var ( externalSigningServer *ExternalSigningServer @@ -356,6 +380,7 @@ func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWr } func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID string, apiRootCA api.RootCA, caRootCA *ca.RootCA, externalCAs ...*api.ExternalCA) *api.Cluster { + fips := strings.HasPrefix(clusterID, "FIPS.") cluster := &api.Cluster{ ID: clusterID, Spec: api.ClusterSpec{ @@ -367,12 +392,13 @@ func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID string, a }, }, RootCA: apiRootCA, + FIPS: fips, } if cluster.RootCA.JoinTokens.Worker == "" { - cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(caRootCA) + cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(caRootCA, fips) } if cluster.RootCA.JoinTokens.Manager == "" { - cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(caRootCA) + cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(caRootCA, fips) } assert.NoError(t, s.Update(func(tx store.Tx) error { store.CreateCluster(tx, cluster) diff --git a/integration/cluster.go b/integration/cluster.go index b36498b0f0..e46e01edbe 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -122,29 +122,10 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { n = node } - c.counter++ - ctx := log.WithLogger(c.ctx, log.L.WithFields( - logrus.Fields{ - "testnode": c.counter, - "testname": c.ctx.Value(testnameKey), - }, - )) - - c.wg.Add(1) - go func() { - c.errs <- n.node.Start(ctx) - c.wg.Done() - }() - - select { - case <-n.node.Ready(): - case <-time.After(opsTimeout): - return fmt.Errorf("node did not ready in time") + if err := c.AddNode(n); err != nil { + return err } - c.nodes[n.node.NodeID()] = n - c.nodesOrder[n.node.NodeID()] = c.counter - if lateBind { // Verify that the control API works if _, err := c.GetClusterInfo(); err != nil { @@ -159,7 +140,6 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { // AddAgent adds node with Agent role(doesn't participate in raft cluster). func (c *testCluster) AddAgent() error { // first node - var n *testNode if len(c.nodes) == 0 { return fmt.Errorf("there is no manager nodes") } @@ -175,29 +155,57 @@ func (c *testCluster) AddAgent() error { if err != nil { return err } - n = node + return c.AddNode(node) +} +// AddNode adds a new node to the cluster +func (c *testCluster) AddNode(n *testNode) error { c.counter++ + if err := c.runNode(n, c.counter); err != nil { + c.counter-- + return err + } + c.nodes[n.node.NodeID()] = n + c.nodesOrder[n.node.NodeID()] = c.counter + return nil +} + +func (c *testCluster) runNode(n *testNode, nodeOrder int) error { ctx := log.WithLogger(c.ctx, log.L.WithFields( logrus.Fields{ - "testnode": c.counter, + "testnode": nodeOrder, "testname": c.ctx.Value(testnameKey), }, )) - c.wg.Add(1) + errCtx, cancel := context.WithCancel(context.Background()) + done := make(chan error) + defer cancel() + defer close(done) + + c.wg.Add(2) go func() { c.errs <- n.node.Start(ctx) c.wg.Done() }() + go func(n *node.Node) { + err := n.Err(errCtx) + select { + case <-errCtx.Done(): + default: + done <- err + } + c.wg.Done() + }(n.node) select { case <-n.node.Ready(): + case err := <-done: + return err case <-time.After(opsTimeout): - return fmt.Errorf("node is not ready in time") + return fmt.Errorf("node did not ready in time") } - c.nodes[n.node.NodeID()] = n - c.nodesOrder[n.node.NodeID()] = c.counter + return nil } @@ -345,40 +353,8 @@ func (c *testCluster) StartNode(id string) error { if !ok { return fmt.Errorf("set node role: node %s not found", id) } - - ctx := log.WithLogger(c.ctx, log.L.WithFields( - logrus.Fields{ - "testnode": c.nodesOrder[id], - "testname": c.ctx.Value(testnameKey), - }, - )) - - errCtx, cancel := context.WithCancel(context.Background()) - done := make(chan error) - defer cancel() - defer close(done) - - c.wg.Add(2) - go func() { - c.errs <- n.node.Start(ctx) - c.wg.Done() - }() - go func(n *node.Node) { - err := n.Err(errCtx) - select { - case <-errCtx.Done(): - default: - done <- err - } - c.wg.Done() - }(n.node) - - select { - case <-n.node.Ready(): - case err := <-done: + if err := c.runNode(n, c.nodesOrder[id]); err != nil { return err - case <-time.After(opsTimeout): - return fmt.Errorf("node did not ready in time") } if n.node.NodeID() != id { return fmt.Errorf("restarted node does not have have the same ID") diff --git a/integration/integration_test.go b/integration/integration_test.go index 81dc6f4000..e1258e5ce7 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + "github.com/docker/swarmkit/node" + "golang.org/x/net/context" "reflect" @@ -857,3 +859,105 @@ func TestNodeJoinWithWrongCerts(t *testing.T) { require.Contains(t, err.Error(), "certificate signed by unknown authority") } } + +// If the cluster does not require FIPS, then any node can join and re-join +// regardless of FIPS mode. +func TestMixedFIPSClusterNonMandatoryFIPS(t *testing.T) { + t.Parallel() + + cl := newTestCluster(t.Name(), false) // no fips + defer func() { + require.NoError(t, cl.Stop()) + }() + // create cluster with a non-FIPS manager, add another non-FIPS manager and a non-FIPs worker + for i := 0; i < 2; i++ { + require.NoError(t, cl.AddManager(false, nil)) + } + require.NoError(t, cl.AddAgent()) + + // add a FIPS manager and FIPS worker + joinAddr, err := cl.RandomManager().node.RemoteAPIAddr() + require.NoError(t, err) + clusterInfo, err := cl.GetClusterInfo() + require.NoError(t, err) + for _, token := range []string{clusterInfo.RootCA.JoinTokens.Worker, clusterInfo.RootCA.JoinTokens.Manager} { + node, err := newTestNode(joinAddr, token, false, true) + require.NoError(t, err) + require.NoError(t, cl.AddNode(node)) + } + + pollClusterReady(t, cl, 2, 3) + + // swap which nodes are fips and which are not - all should start up just fine + for nodeID, n := range cl.nodes { + n.config.FIPS = !n.config.FIPS + require.NoError(t, n.Pause(false)) + require.NoError(t, cl.StartNode(nodeID)) + } + + pollClusterReady(t, cl, 2, 3) +} + +// If the cluster require FIPS, then only FIPS nodes can join and re-join. +func TestMixedFIPSClusterMandatoryFIPS(t *testing.T) { + t.Parallel() + + cl := newTestCluster(t.Name(), true) + defer func() { + require.NoError(t, cl.Stop()) + }() + for i := 0; i < 3; i++ { + require.NoError(t, cl.AddManager(false, nil)) + } + require.NoError(t, cl.AddAgent()) + + pollClusterReady(t, cl, 1, 3) + + // restart a manager and restart the worker in non-FIPS mode - both will fail, but restarting it in FIPS mode + // will succeed + leader, err := cl.Leader() + require.NoError(t, err) + var nonLeader, worker *testNode + for _, n := range cl.nodes { + if n == leader { + continue + } + if nonLeader == nil && n.IsManager() { + nonLeader = n + } + if worker == nil && !n.IsManager() { + worker = n + } + } + for _, n := range []*testNode{nonLeader, worker} { + nodeID := n.node.NodeID() + rAddr := "" + if n.IsManager() { + // make sure to save the old address because if a node is stopped, we can't get the node address, and it gets set to + // a completely new address, which will break raft in the case of a manager + rAddr, err = n.node.RemoteAPIAddr() + require.NoError(t, err) + } + require.NoError(t, n.Pause(false)) + n.config.FIPS = false + require.Equal(t, node.ErrMandatoryFIPS, cl.StartNode(nodeID)) + + require.NoError(t, n.Pause(false)) + n.config.FIPS = true + n.config.ListenRemoteAPI = rAddr + require.NoError(t, cl.StartNode(nodeID)) + } + + pollClusterReady(t, cl, 1, 3) + + // try to add a non-FIPS manager and non-FIPS worker - it won't work + joinAddr, err := cl.RandomManager().node.RemoteAPIAddr() + require.NoError(t, err) + clusterInfo, err := cl.GetClusterInfo() + require.NoError(t, err) + for _, token := range []string{clusterInfo.RootCA.JoinTokens.Worker, clusterInfo.RootCA.JoinTokens.Manager} { + n, err := newTestNode(joinAddr, token, false, false) + require.NoError(t, err) + require.Equal(t, node.ErrMandatoryFIPS, cl.AddNode(n)) + } +} diff --git a/manager/controlapi/cluster.go b/manager/controlapi/cluster.go index 0876113d70..adc0c1a485 100644 --- a/manager/controlapi/cluster.go +++ b/manager/controlapi/cluster.go @@ -123,10 +123,10 @@ func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRe expireBlacklistedCerts(cluster) if request.Rotation.WorkerJoinToken { - cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(&rootCA) + cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(&rootCA, cluster.FIPS) } if request.Rotation.ManagerJoinToken { - cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(&rootCA) + cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(&rootCA, cluster.FIPS) } updatedRootCA, err := validateCAConfig(ctx, s.securityConfig, cluster) diff --git a/manager/controlapi/cluster_test.go b/manager/controlapi/cluster_test.go index e9c30ce25e..ad05a13596 100644 --- a/manager/controlapi/cluster_test.go +++ b/manager/controlapi/cluster_test.go @@ -46,8 +46,8 @@ func createClusterObj(id, name string, policy api.AcceptancePolicy, rootCA *ca.R CAKey: key, CACertHash: rootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: ca.GenerateJoinToken(rootCA), - Manager: ca.GenerateJoinToken(rootCA), + Worker: ca.GenerateJoinToken(rootCA, false), + Manager: ca.GenerateJoinToken(rootCA, false), }, }, } diff --git a/manager/manager.go b/manager/manager.go index 677c9ae3be..a7e9508265 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -1050,8 +1050,8 @@ func defaultClusterObject( CACert: rootCA.Certs, CACertHash: rootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: ca.GenerateJoinToken(rootCA), - Manager: ca.GenerateJoinToken(rootCA), + Worker: ca.GenerateJoinToken(rootCA, fips), + Manager: ca.GenerateJoinToken(rootCA, fips), }, }, UnlockKeys: initialUnlockKeys, diff --git a/node/node.go b/node/node.go index a6ab3035af..8674f40f77 100644 --- a/node/node.go +++ b/node/node.go @@ -15,6 +15,7 @@ import ( "time" "github.com/docker/swarmkit/ca/keyutils" + "github.com/docker/swarmkit/identity" "github.com/boltdb/bolt" "github.com/docker/docker/pkg/plugingetter" @@ -54,6 +55,9 @@ var ( // ErrInvalidUnlockKey is returned when we can't decrypt the TLS certificate ErrInvalidUnlockKey = errors.New("node is locked, and needs a valid unlock key") + + // ErrMandatoryFIPS is returned when the cluster we are joining mandates FIPS, but we are running in non-FIPS mode + ErrMandatoryFIPS = errors.New("node is not FIPS-enabled but cluster requires FIPS") ) func init() { @@ -666,6 +670,25 @@ func (n *Node) Remotes() []api.Peer { return remotes } +// Given a cluster ID, returns whether the cluster ID indicates that the cluster +// mandates FIPS mode. These cluster IDs start with "FIPS." as a prefix. +func isMandatoryFIPSClusterID(securityConfig *ca.SecurityConfig) bool { + return strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.") +} + +// Given a join token, returns whether it indicates that the cluster mandates FIPS +// mode. +func isMandatoryFIPSClusterJoinToken(joinToken string) bool { + if parsed, err := ca.ParseJoinToken(joinToken); err == nil { + return parsed.FIPS + } + return false +} + +func generateFIPSClusterID() string { + return "FIPS." + identity.NewID() +} + func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigPaths) (*ca.SecurityConfig, func() error, error) { var ( securityConfig *ca.SecurityConfig @@ -716,6 +739,10 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } log.G(ctx).Debug("generated CA key and certificate") } else if err == ca.ErrNoLocalRootCA { // from previous error loading the root CA from disk + // if we are attempting to join another cluster, which has a FIPS join token, and we are not FIPS, error + if n.config.JoinAddr != "" && isMandatoryFIPSClusterJoinToken(n.config.JoinToken) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } rootCA, err = ca.DownloadRootCA(ctx, paths.RootCA, n.config.JoinToken, n.connBroker) if err != nil { return nil, nil, err @@ -740,11 +767,21 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } log.G(ctx).WithError(err).Debugf("no node credentials found in: %s", krw.Target()) - securityConfig, cancel, err = rootCA.CreateSecurityConfig(ctx, krw, ca.CertificateRequestConfig{ + // if we are attempting to join another cluster, which has a FIPS join token, and we are not FIPS, error + if n.config.JoinAddr != "" && isMandatoryFIPSClusterJoinToken(n.config.JoinToken) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } + + requestConfig := ca.CertificateRequestConfig{ Token: n.config.JoinToken, Availability: n.config.Availability, ConnBroker: n.connBroker, - }) + } + // If this is a new cluster, we want to name the cluster ID "FIPS-something" + if n.config.FIPS { + requestConfig.Organization = generateFIPSClusterID() + } + securityConfig, cancel, err = rootCA.CreateSecurityConfig(ctx, krw, requestConfig) if err != nil { return nil, nil, err @@ -752,6 +789,10 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } } + if isMandatoryFIPSClusterID(securityConfig) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } + n.Lock() n.role = securityConfig.ClientTLSCreds.Role() n.nodeID = securityConfig.ClientTLSCreds.NodeID() diff --git a/node/node_test.go b/node/node_test.go index 3c3d8d5b63..ebd44b03e8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" "time" @@ -275,6 +276,162 @@ func TestLoadSecurityConfigDownloadAllCerts(t *testing.T) { require.NoError(t, err) } +// If there is nothing on disk and no join addr, and FIPS is enabled, we create a cluster whose +// ID starts with 'FIPS.' +func TestLoadSecurityConfigNodeFIPSCreateCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-new-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates")) + + tc := cautils.NewTestCA(t) + defer tc.Stop() + + config := &Config{ + StateDir: tempdir, + FIPS: true, + } + + node, err := New(config) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) +} + +// If FIPS is enabled and there is a join address, the cluster ID is whatever the CA set +// the cluster ID to. +func TestLoadSecurityConfigNodeFIPSJoinCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-join-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + certDir := filepath.Join(tempdir, "certificates") + paths := ca.NewConfigPaths(certDir) + + for _, fips := range []bool{true, false} { + require.NoError(t, os.RemoveAll(certDir)) + + var tc *cautils.TestCA + if fips { + tc = cautils.NewFIPSTestCA(t) + } else { + tc = cautils.NewTestCA(t) + } + defer tc.Stop() + + peer, err := tc.ConnBroker.Remotes().Select() + require.NoError(t, err) + + node, err := New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tc.ManagerToken, + FIPS: true, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.Equal(t, fips, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) + } +} + +// If the certificate specifies that the cluster requires FIPS mode, loading the security +// config will fail if the node is not FIPS enabled. +func TestLoadSecurityConfigRespectsFIPSCert(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-cert-on-disk") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + tc := cautils.NewFIPSTestCA(t) + defer tc.Stop() + + certDir := filepath.Join(tempdir, "certificates") + require.NoError(t, os.Mkdir(certDir, 0700)) + paths := ca.NewConfigPaths(certDir) + + // copy certs and keys from the test CA using a hard link + _, err = tc.WriteNewNodeConfig(ca.ManagerRole) + require.NoError(t, err) + require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert)) + require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key)) + require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert)) + + node, err := New(&Config{StateDir: tempdir}) + require.NoError(t, err) + _, _, err = node.loadSecurityConfig(tc.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) + + node, err = New(&Config{ + StateDir: tempdir, + FIPS: true, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) +} + +// If FIPS is disabled and there is a join address and token, and the join token indicates +// the cluster requires fips, then loading the security config will fail. However, if +// there are already certs on disk, it will load them and ignore the join token. +func TestLoadSecurityConfigNonFIPSNodeJoinCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-nonfips-join-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + certDir := filepath.Join(tempdir, "certificates") + require.NoError(t, os.Mkdir(certDir, 0700)) + paths := ca.NewConfigPaths(certDir) + + tc := cautils.NewTestCA(t) + defer tc.Stop() + // copy certs and keys from the test CA using a hard link + _, err = tc.WriteNewNodeConfig(ca.ManagerRole) + require.NoError(t, err) + require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert)) + require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key)) + require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert)) + + tcFIPS := cautils.NewFIPSTestCA(t) + defer tcFIPS.Stop() + + peer, err := tcFIPS.ConnBroker.Remotes().Select() + require.NoError(t, err) + + node, err := New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tcFIPS.ManagerToken, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tcFIPS.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.False(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) + + // remove the node cert only - now that the node has to download the certs, it will check the + // join address and fail + require.NoError(t, os.Remove(paths.Node.Cert)) + + _, _, err = node.loadSecurityConfig(tcFIPS.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) + + // remove all the certs (CA and node) - the node will also check the join address and fail + require.NoError(t, os.RemoveAll(certDir)) + + _, _, err = node.loadSecurityConfig(tcFIPS.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) +} + func TestManagerRespectsDispatcherRootCAUpdate(t *testing.T) { tmpDir, err := ioutil.TempDir("", "manager-root-ca-update") require.NoError(t, err) From 1a9dd8e6beaed8fd5c7b930efcb1cd4e9603185a Mon Sep 17 00:00:00 2001 From: Ying Li Date: Mon, 2 Apr 2018 12:03:02 -0700 Subject: [PATCH 14/15] Propagate the FIPS bool to the raft DEK manager so that the DEK is encrypted using fernet. Signed-off-by: Ying Li (cherry picked from commit 905d35b089854c735ee8d690e913414a5fc0c30a) --- manager/deks.go | 36 +- manager/deks_test.go | 727 +++++++++++++++++++++++----------------- manager/manager.go | 2 +- manager/manager_test.go | 13 +- 4 files changed, 453 insertions(+), 325 deletions(-) diff --git a/manager/deks.go b/manager/deks.go index edb5227904..f3d985a137 100644 --- a/manager/deks.go +++ b/manager/deks.go @@ -22,6 +22,10 @@ const ( type RaftDEKData struct { raft.EncryptionKeys NeedsRotation bool + + // The FIPS boolean is not serialized, but is internal state which indicates how + // the raft DEK headers should be encrypted (e.g. using FIPS compliant algorithms) + FIPS bool } // UnmarshalHeaders loads the state of the DEK manager given the current TLS headers @@ -32,13 +36,13 @@ func (r RaftDEKData) UnmarshalHeaders(headers map[string]string, kekData ca.KEKD ) if currentDEKStr, ok := headers[pemHeaderRaftDEK]; ok { - currentDEK, err = decodePEMHeaderValue(currentDEKStr, kekData.KEK) + currentDEK, err = decodePEMHeaderValue(currentDEKStr, kekData.KEK, r.FIPS) if err != nil { return nil, err } } if pendingDEKStr, ok := headers[pemHeaderRaftPendingDEK]; ok { - pendingDEK, err = decodePEMHeaderValue(pendingDEKStr, kekData.KEK) + pendingDEK, err = decodePEMHeaderValue(pendingDEKStr, kekData.KEK, r.FIPS) if err != nil { return nil, err } @@ -55,6 +59,7 @@ func (r RaftDEKData) UnmarshalHeaders(headers map[string]string, kekData ca.KEKD CurrentDEK: currentDEK, PendingDEK: pendingDEK, }, + FIPS: r.FIPS, }, nil } @@ -66,7 +71,7 @@ func (r RaftDEKData) MarshalHeaders(kekData ca.KEKData) (map[string]string, erro pemHeaderRaftPendingDEK: r.PendingDEK, } { if contents != nil { - dekStr, err := encodePEMHeaderValue(contents, kekData.KEK) + dekStr, err := encodePEMHeaderValue(contents, kekData.KEK, r.FIPS) if err != nil { return nil, err } @@ -88,6 +93,7 @@ func (r RaftDEKData) UpdateKEK(oldKEK, candidateKEK ca.KEKData) ca.PEMKeyHeaders return RaftDEKData{ EncryptionKeys: r.EncryptionKeys, NeedsRotation: true, + FIPS: r.FIPS, } } return r @@ -112,6 +118,7 @@ func compareKEKs(oldKEK, candidateKEK ca.KEKData) (bool, bool, error) { type RaftDEKManager struct { kw ca.KeyWriter rotationCh chan struct{} + FIPS bool } var errNoUpdateNeeded = fmt.Errorf("don't need to rotate or update") @@ -122,7 +129,7 @@ var errNotUsingRaftDEKData = fmt.Errorf("RaftDEKManager can no longer store and // NewRaftDEKManager returns a RaftDEKManager that uses the current key writer // and header manager -func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { +func NewRaftDEKManager(kw ca.KeyWriter, fips bool) (*RaftDEKManager, error) { // If there is no current DEK, generate one and write it to disk err := kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { dekData, ok := h.(RaftDEKData) @@ -132,6 +139,7 @@ func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { EncryptionKeys: raft.EncryptionKeys{ CurrentDEK: encryption.GenerateSecretKey(), }, + FIPS: fips, }, nil } return nil, errNoUpdateNeeded @@ -141,6 +149,7 @@ func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { } return &RaftDEKManager{ kw: kw, + FIPS: fips, rotationCh: make(chan struct{}, 1), }, nil } @@ -156,8 +165,9 @@ func (r *RaftDEKManager) NeedsRotation() bool { } // GetKeys returns the current set of DEKs. If NeedsRotation is true, and there -// is no existing PendingDEK, it will try to create one. If there are any errors -// doing so, just return the original. +// is no existing PendingDEK, it will try to create one. If it successfully creates +// and writes a PendingDEK, it sets NeedRotation to false. If there are any errors +// doing so, just return the original set of keys. func (r *RaftDEKManager) GetKeys() raft.EncryptionKeys { var newKeys, originalKeys raft.EncryptionKeys err := r.kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { @@ -173,7 +183,10 @@ func (r *RaftDEKManager) GetKeys() raft.EncryptionKeys { CurrentDEK: data.CurrentDEK, PendingDEK: encryption.GenerateSecretKey(), } - return RaftDEKData{EncryptionKeys: newKeys}, nil + return RaftDEKData{ + EncryptionKeys: newKeys, + FIPS: data.FIPS, + }, nil }) if err != nil { return originalKeys @@ -202,6 +215,7 @@ func (r *RaftDEKManager) UpdateKeys(newKeys raft.EncryptionKeys) error { return RaftDEKData{ EncryptionKeys: newKeys, NeedsRotation: data.NeedsRotation, + FIPS: data.FIPS, }, nil }) } @@ -240,10 +254,10 @@ func (r *RaftDEKManager) MaybeUpdateKEK(candidateKEK ca.KEKData) (bool, bool, er return updated, unlockedToLocked, err } -func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { +func decodePEMHeaderValue(headerValue string, kek []byte, fips bool) ([]byte, error) { var decrypter encryption.Decrypter = encryption.NoopCrypter if kek != nil { - _, decrypter = encryption.Defaults(kek, false) + _, decrypter = encryption.Defaults(kek, fips) } valueBytes, err := base64.StdEncoding.DecodeString(headerValue) if err != nil { @@ -256,10 +270,10 @@ func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { return result, nil } -func encodePEMHeaderValue(headerValue []byte, kek []byte) (string, error) { +func encodePEMHeaderValue(headerValue []byte, kek []byte, fips bool) (string, error) { var encrypter encryption.Encrypter = encryption.NoopCrypter if kek != nil { - encrypter, _ = encryption.Defaults(kek, false) + encrypter, _ = encryption.Defaults(kek, fips) } encrypted, err := encryption.Encrypt(headerValue, encrypter) if err != nil { diff --git a/manager/deks_test.go b/manager/deks_test.go index 4873b05c62..63fdaf9141 100644 --- a/manager/deks_test.go +++ b/manager/deks_test.go @@ -3,6 +3,7 @@ package manager import ( "encoding/base64" "encoding/pem" + "fmt" "io/ioutil" "os" "testing" @@ -16,101 +17,121 @@ import ( // Tests updating a kek on a raftDEK object. func TestRaftDEKUpdateKEK(t *testing.T) { - startData := RaftDEKData{ - EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + for _, fips := range []bool{true, false} { + startData := RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + FIPS: fips, + } + startKEK := ca.KEKData{} + + // because UpdateKEK returns a PEMKeyHeaders interface, we need to cast to check + // values + updateDEKAndCast := func(dekdata RaftDEKData, oldKEK ca.KEKData, newKEK ca.KEKData) RaftDEKData { + result := dekdata.UpdateKEK(oldKEK, newKEK) + raftDekObj, ok := result.(RaftDEKData) + require.True(t, ok) + return raftDekObj + } + + // nothing changes if we are updating a kek and they're both nil + result := updateDEKAndCast(startData, startKEK, ca.KEKData{Version: 2}) + require.Equal(t, result, startData) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // when moving from unlocked to locked, a "needs rotation" header is generated but no + // pending header is generated + updatedKEK := ca.KEKData{KEK: []byte("something"), Version: 1} + result = updateDEKAndCast(startData, startKEK, updatedKEK) + require.NotEqual(t, startData, result) + require.True(t, result.NeedsRotation) + require.Equal(t, startData.CurrentDEK, result.CurrentDEK) + require.Nil(t, result.PendingDEK) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // this is whether or not pending exists + startData.PendingDEK = []byte("pending") + result = updateDEKAndCast(startData, startKEK, updatedKEK) + require.NotEqual(t, startData, result) + require.True(t, result.NeedsRotation) + require.Equal(t, startData.CurrentDEK, result.CurrentDEK) + require.Equal(t, startData.PendingDEK, result.PendingDEK) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // if we are going from locked to unlocked, nothing happens + result = updateDEKAndCast(startData, updatedKEK, startKEK) + require.Equal(t, startData, result) + require.False(t, result.NeedsRotation) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // if we are going to locked to another locked, nothing happens + result = updateDEKAndCast(startData, updatedKEK, ca.KEKData{KEK: []byte("other"), Version: 4}) + require.Equal(t, startData, result) + require.False(t, result.NeedsRotation) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed } - startKEK := ca.KEKData{} - - // because UpdateKEK returns a PEMKeyHeaders interface, we need to cast to check - // values - updateDEKAndCast := func(dekdata RaftDEKData, oldKEK ca.KEKData, newKEK ca.KEKData) RaftDEKData { - result := dekdata.UpdateKEK(oldKEK, newKEK) - raftDekObj, ok := result.(RaftDEKData) - require.True(t, ok) - return raftDekObj - } - - // nothing changes if we are updating a kek and they're both nil - result := updateDEKAndCast(startData, startKEK, ca.KEKData{Version: 2}) - require.Equal(t, result, startData) - - // when moving from unlocked to locked, a "needs rotation" header is generated but no - // pending header is generated - updatedKEK := ca.KEKData{KEK: []byte("something"), Version: 1} - result = updateDEKAndCast(startData, startKEK, updatedKEK) - require.NotEqual(t, startData, result) - require.True(t, result.NeedsRotation) - require.Equal(t, startData.CurrentDEK, result.CurrentDEK) - require.Nil(t, result.PendingDEK) - - // this is whether or not pending exists - startData.PendingDEK = []byte("pending") - result = updateDEKAndCast(startData, startKEK, updatedKEK) - require.NotEqual(t, startData, result) - require.True(t, result.NeedsRotation) - require.Equal(t, startData.CurrentDEK, result.CurrentDEK) - require.Equal(t, startData.PendingDEK, result.PendingDEK) - - // if we are going from locked to unlocked, nothing happens - result = updateDEKAndCast(startData, updatedKEK, startKEK) - require.Equal(t, startData, result) - require.False(t, result.NeedsRotation) - - // if we are going to locked to another locked, nothing happens - result = updateDEKAndCast(startData, updatedKEK, ca.KEKData{KEK: []byte("other"), Version: 4}) - require.Equal(t, startData, result) - require.False(t, result.NeedsRotation) } func TestRaftDEKMarshalUnmarshal(t *testing.T) { - startData := RaftDEKData{ - EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, - } - kek := ca.KEKData{} - - headers, err := startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 1) - - // can't unmarshal with the wrong kek - _, err = RaftDEKData{}.UnmarshalHeaders(headers, ca.KEKData{KEK: []byte("something")}) - require.Error(t, err) - - // we can unmarshal what was marshalled with the right kek - toData, err := RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // try the other headers as well - startData.PendingDEK = []byte("Hello") - headers, err = startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 2) - - // we can unmarshal what was marshalled - toData, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // try the other headers as well - startData.NeedsRotation = true - startData.PendingDEK = nil - headers, err = startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 2) - - // we can unmarshal what was marshalled - toData, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // If there is a pending header, but no current header, set will fail - headers = map[string]string{ - pemHeaderRaftPendingDEK: headers[pemHeaderRaftDEK], + for _, fips := range []bool{true, false} { + startData := RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + FIPS: fips, + } + kek := ca.KEKData{} + + headers, err := startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 1) + + // can't unmarshal with the wrong kek + _, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, ca.KEKData{KEK: []byte("something")}) + require.Error(t, err) + + // we can unmarshal what was marshalled with the right kek + toData, err := RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok := toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // try the other headers as well + startData.PendingDEK = []byte("Hello") + headers, err = startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 2) + + // we can unmarshal what was marshalled + toData, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok = toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // try the other headers as well + startData.NeedsRotation = true + startData.PendingDEK = nil + headers, err = startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 2) + + // we can unmarshal what was marshalled + toData, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok = toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // If there is a pending header, but no current header, set will fail + headers = map[string]string{ + pemHeaderRaftPendingDEK: headers[pemHeaderRaftDEK], + } + _, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.Error(t, err) + require.Contains(t, err.Error(), "pending DEK, but no current DEK") } - _, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.Error(t, err) - require.Contains(t, err.Error(), "pending DEK, but no current DEK") } // NewRaftDEKManager creates a key if one doesn't exist @@ -123,38 +144,50 @@ func TestNewRaftDEKManager(t *testing.T) { cert, key, err := cautils.CreateRootCertAndKey("cn") require.NoError(t, err) - krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - require.NoError(t, krw.Write(cert, key, nil)) + for _, fips := range []bool{true, false} { + krw := ca.NewKeyReadWriter(paths.Node, nil, nil) + require.NoError(t, krw.Write(cert, key, nil)) - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotContains(t, string(keyBytes), pemHeaderRaftDEK) // headers are not written + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotContains(t, string(keyBytes), pemHeaderRaftDEK) // headers are not written - dekManager, err := NewRaftDEKManager(krw) // this should create a new DEK and write it to the file - require.NoError(t, err) + dekManager, err := NewRaftDEKManager(krw, fips) // this should create a new DEK and write it to the file + require.NoError(t, err) - keyBytes, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Contains(t, string(keyBytes), pemHeaderRaftDEK) // header is written now + keyBytes, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Contains(t, string(keyBytes), pemHeaderRaftDEK) // header is written now - keys := dekManager.GetKeys() - require.NotNil(t, keys.CurrentDEK) - require.Nil(t, keys.PendingDEK) - require.False(t, dekManager.NeedsRotation()) + // ensure that the created raft DEK uses FIPS + h, _ := krw.GetCurrentState() + casted, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) - // If one exists, nothing is updated - dekManager, err = NewRaftDEKManager(krw) // this should create a new DEK and write it to the file - require.NoError(t, err) + keys := dekManager.GetKeys() + require.NotNil(t, keys.CurrentDEK) + require.Nil(t, keys.PendingDEK) + require.False(t, dekManager.NeedsRotation()) - keyBytes2, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) + // If one exists, nothing is updated + dekManager, err = NewRaftDEKManager(krw, fips) // this should not have created a new dek + require.NoError(t, err) - require.Equal(t, keys, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + keyBytes2, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) + + require.Equal(t, keys, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) + } } -// NeedsRotate returns true if there is a PendingDEK or a NeedsRotation flag +// NeedsRotate returns true if there is a PendingDEK or a NeedsRotation flag. GetKeys() evaluates +// whether a PendingDEK is there, and if there's no pending DEK but there is a NeedsRotation flag, +// it creates a PendingDEK and removes the NeedsRotation flag. If both the PendingDEK and +// NeedsRotation flag are there, it does not remove the NeedsRotation flag, because that indicates +// that we basically need to do 2 rotations. func TestRaftDEKManagerNeedsRotateGetKeys(t *testing.T) { tempDir, err := ioutil.TempDir("", "manager-maybe-get-data-") require.NoError(t, err) @@ -162,80 +195,110 @@ func TestRaftDEKManagerNeedsRotateGetKeys(t *testing.T) { paths := ca.NewConfigPaths(tempDir) - // if there is no PendingDEK, and no NeedsRotation flag: NeedsRotation=false - keys := raft.EncryptionKeys{CurrentDEK: []byte("hello")} - dekManager, err := NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys})) - require.NoError(t, err) - - require.False(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there is a PendingDEK, and no NeedsRotation flag: NeedsRotation=true - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello"), PendingDEK: []byte("another")} - dekManager, err = NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys})) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there is a PendingDEK, and a NeedsRotation flag: NeedsRotation=true - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello"), PendingDEK: []byte("another")} - dekManager, err = NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - })) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there no PendingDEK, and a NeedsRotation flag: NeedsRotation=true and - // GetKeys attempts to create a pending key and write it to disk. However, writing - // will error (because there is no key on disk atm), and then the original keys will - // be returned. - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello")} - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - dekManager, err = NewRaftDEKManager(krw) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.True(t, dekData.NeedsRotation) - - // if there no PendingDEK, and a NeedsRotation flag: NeedsRotation=true and - // GetKeys attempts to create a pending key and write it to disk. If successful, - // it returns the new keys - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello")} - krw = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - dekManager, err = NewRaftDEKManager(krw) - - require.NoError(t, err) - cert, key, err := cautils.CreateRootCertAndKey("cn") - require.NoError(t, err) - require.NoError(t, krw.Write(cert, key, nil)) - - require.True(t, dekManager.NeedsRotation()) - updatedKeys := dekManager.GetKeys() - require.Equal(t, keys.CurrentDEK, updatedKeys.CurrentDEK) - require.NotNil(t, updatedKeys.PendingDEK) - require.True(t, dekManager.NeedsRotation()) - - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.False(t, dekData.NeedsRotation) + for _, fips := range []bool{true, false} { + for _, testcase := range []struct { + description string + dekData RaftDEKData + managerNeedsRotation bool + newDEKDataNeedsRotation bool + keyOnDisk bool + }{ + { + description: "if there is no PendingDEK, and no NeedsRotation flag: NeedsRotation()->false, DEKData.NeedsRotation->false", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: false, + }, + managerNeedsRotation: false, + newDEKDataNeedsRotation: false, + }, + { + description: "if there is a PendingDEK, and no NeedsRotation flag: NeedsRotation()->true, DEKData.NeedsRotation->false", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{ + CurrentDEK: []byte("hello"), + PendingDEK: []byte("another"), + }, + NeedsRotation: false, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: false, + }, + { + description: "if there is a PendingDEK, and a NeedsRotation flag: NeedsRotation()->true, DEKData.NeedsRotation->true", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{ + CurrentDEK: []byte("hello"), + PendingDEK: []byte("another"), + }, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: true, + }, + // These in these two cases, the original keys did not have pending keys. GetKeys + // should create them, but only if it can write the new pending key to the disk. + { + description: ` + if there no PendingDEK, and a NeedsRotation flag: NeedsRotation()->true and + GetKeys attempts to create a pending key and write it to disk. However, writing + will error (because there is no key on disk atm), and then the original keys will + be returned. So DEKData.NeedsRotation->true.`, + keyOnDisk: false, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: true, + }, + { + description: ` + if there no PendingDEK, and there is a NeedsRotation flag: NeedsRotation()->true and + GetKeys attempts to create a pending key and write it to disk. Once a pending key is + created, the NeedsRotation flag can be set to false. So DEKData.NeedsRotation->false`, + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: false, + }, + } { + // clear the directory + require.NoError(t, os.RemoveAll(tempDir)) + os.Mkdir(tempDir, 0777) + testcase.dekData.FIPS = fips + krw := ca.NewKeyReadWriter(paths.Node, nil, testcase.dekData) + if testcase.keyOnDisk { + cert, key, err := cautils.CreateRootCertAndKey("cn") + require.NoError(t, err) + require.NoError(t, krw.Write(cert, key, nil)) + } + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + require.Equal(t, testcase.managerNeedsRotation, dekManager.NeedsRotation(), testcase.description) + + gotKeys := dekManager.GetKeys() + if testcase.dekData.NeedsRotation && testcase.dekData.EncryptionKeys.PendingDEK == nil && testcase.keyOnDisk { + require.Equal(t, testcase.dekData.EncryptionKeys.CurrentDEK, gotKeys.CurrentDEK, testcase.description) + require.NotNil(t, gotKeys.PendingDEK, testcase.description) + } else { + require.Equal(t, testcase.dekData.EncryptionKeys, gotKeys, testcase.description) + } + + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, testcase.newDEKDataNeedsRotation, dekData.NeedsRotation, + "(FIPS: %v) %s", fips, testcase.description) + } + } } func TestRaftDEKManagerUpdateKeys(t *testing.T) { @@ -251,42 +314,46 @@ func TestRaftDEKManagerUpdateKeys(t *testing.T) { CurrentDEK: []byte("key1"), PendingDEK: []byte("key2"), } - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - require.NoError(t, krw.Write(cert, key, nil)) + for _, fips := range []bool{true, false} { + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ + EncryptionKeys: keys, + NeedsRotation: true, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err := NewRaftDEKManager(krw) - require.NoError(t, err) + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) - newKeys := raft.EncryptionKeys{ - CurrentDEK: []byte("new current"), - } - require.NoError(t, dekManager.UpdateKeys(newKeys)) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + newKeys := raft.EncryptionKeys{ + CurrentDEK: []byte("new current"), + } + require.NoError(t, dekManager.UpdateKeys(newKeys)) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.True(t, dekData.NeedsRotation) + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) - // UpdateKeys so there is no CurrentDEK: all the headers should be wiped out - require.NoError(t, dekManager.UpdateKeys(raft.EncryptionKeys{})) - require.Equal(t, raft.EncryptionKeys{}, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + // UpdateKeys so there is no CurrentDEK: all the headers should be wiped out + require.NoError(t, dekManager.UpdateKeys(raft.EncryptionKeys{})) + require.Equal(t, raft.EncryptionKeys{}, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) - h, _ = krw.GetCurrentState() - require.Nil(t, h) + h, _ = krw.GetCurrentState() + require.Nil(t, h) - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - keyBlock, _ := pem.Decode(keyBytes) - require.NotNil(t, keyBlock) + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + keyBlock, _ := pem.Decode(keyBytes) + require.NotNil(t, keyBlock) - // the only header remaining should be the kek version - require.Len(t, keyBlock.Headers, 1) - require.Contains(t, keyBlock.Headers, "kek-version") + // the only header remaining should be the kek version + require.Len(t, keyBlock.Headers, 1) + require.Contains(t, keyBlock.Headers, "kek-version") + } } func TestRaftDEKManagerMaybeUpdateKEK(t *testing.T) { @@ -300,101 +367,112 @@ func TestRaftDEKManagerMaybeUpdateKEK(t *testing.T) { keys := raft.EncryptionKeys{CurrentDEK: []byte("current dek")} - // trying to update a KEK will error if the version is the same but the kek is different - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys}) - require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err := NewRaftDEKManager(krw) - require.NoError(t, err) - - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - - _, _, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now")}) - require.Error(t, err) - require.False(t, dekManager.NeedsRotation()) - - keyBytes2, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) - - // trying to update a KEK from unlocked to lock will set NeedsRotation to true, as well as encrypt the TLS key - updated, unlockedToLocked, err := dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 1}) - require.NoError(t, err) - require.True(t, updated) - require.True(t, unlockedToLocked) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - require.NotNil(t, <-dekManager.RotationNotify()) // we are notified of a new pending key - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotEqual(t, keyBytes, keyBytes2) - keyBytes = keyBytes2 - - readKRW := ca.NewKeyReadWriter(paths.Node, []byte("locked now"), RaftDEKData{}) - _, _, err = readKRW.Read() - require.NoError(t, err) - - // trying to update a KEK of a lower version will not update anything, but will not error - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.False(t, updated) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2, string(keyBytes), string(keyBytes2)) - - // updating a kek to a higher version, but with the same kek, will also neither update anything nor error - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 100}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.False(t, updated) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) - - // going from locked to unlock does not result in the NeedsRotation flag, but does result in - // the key being decrypted - krw = ca.NewKeyReadWriter(paths.Node, []byte("kek"), RaftDEKData{EncryptionKeys: keys}) - require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err = NewRaftDEKManager(krw) - require.NoError(t, err) - - keyBytes, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) + for _, fips := range []bool{true, false} { + // trying to update a KEK will error if the version is the same but the kek is different + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ + EncryptionKeys: keys, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + + _, _, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now")}) + require.Error(t, err) + require.False(t, dekManager.NeedsRotation()) + + keyBytes2, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) + + // trying to update a KEK from unlocked to lock will set NeedsRotation to true, as well as encrypt the TLS key + updated, unlockedToLocked, err := dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 1}) + require.NoError(t, err) + require.True(t, updated) + require.True(t, unlockedToLocked) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) + require.NotNil(t, <-dekManager.RotationNotify()) // we are notified of a new pending key + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotEqual(t, keyBytes, keyBytes2) + keyBytes = keyBytes2 + + readKRW := ca.NewKeyReadWriter(paths.Node, []byte("locked now"), RaftDEKData{FIPS: fips}) + _, _, err = readKRW.Read() + require.NoError(t, err) + + // trying to update a KEK of a lower version will not update anything, but will not error + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.False(t, updated) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ = krw.GetCurrentState() + dekData, ok = h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2, string(keyBytes), string(keyBytes2)) + + // updating a kek to a higher version, but with the same kek, will also neither update anything nor error + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 100}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.False(t, updated) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ = krw.GetCurrentState() + dekData, ok = h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{Version: 2}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.True(t, updated) - require.Equal(t, keys, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotEqual(t, keyBytes, keyBytes2) - - readKRW = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{}) - _, _, err = readKRW.Read() - require.NoError(t, err) + // going from locked to unlock does not result in the NeedsRotation flag, but does result in + // the key being decrypted + krw = ca.NewKeyReadWriter(paths.Node, []byte("kek"), RaftDEKData{ + EncryptionKeys: keys, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) + dekManager, err = NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + keyBytes, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{Version: 2}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.True(t, updated) + require.Equal(t, keys, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotEqual(t, keyBytes, keyBytes2) + + readKRW = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{FIPS: fips}) + _, _, err = readKRW.Read() + require.NoError(t, err) + } } // The TLS KEK and the KEK for the headers should be in sync, and so failing @@ -461,3 +539,34 @@ O0T3aXuZGYNyh//KqAoA3erCmh6HauMz84Y= _, _, err = krw.Read() require.NoError(t, err) } + +// If FIPS is enabled, the raft DEK will be encrypted using fernet, and not NACL secretbox. +func TestRaftDEKsFIPSEnabledUsesFernet(t *testing.T) { + tempDir, err := ioutil.TempDir("", "manager-dek-fips") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + paths := ca.NewConfigPaths(tempDir) + cert, key, err := cautils.CreateRootCertAndKey("cn") + require.NoError(t, err) + + // no particular reason not to use FIPS in the key writer to write the TLS key itself, + // except to demonstrate that these two functionalities are decoupled + keys := raft.EncryptionKeys{CurrentDEK: []byte("current dek")} + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys, FIPS: true}) + require.NoError(t, krw.Write(cert, key, nil)) + + dekManager, err := NewRaftDEKManager(krw, true) // this should be able to read the dek data + require.NoError(t, err) + require.Equal(t, keys, dekManager.GetKeys()) + + // if we do not use FIPS to write the header in the first place, a FIPS DEK manager can't read it + // because it's NACL secretbox + keys = raft.EncryptionKeys{CurrentDEK: []byte("current dek")} + krw = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys}) + require.NoError(t, krw.Write(cert, key, nil)) + + dekManager, err = NewRaftDEKManager(krw, true) // this should be able to read the dek data + require.NoError(t, err) + fmt.Println(err) +} diff --git a/manager/manager.go b/manager/manager.go index a7e9508265..3d68a2698a 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -213,7 +213,7 @@ func New(config *Config) (*Manager, error) { raftCfg.HeartbeatTick = int(config.HeartbeatTick) } - dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter()) + dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter(), false) if err != nil { return nil, err } diff --git a/manager/manager_test.go b/manager/manager_test.go index 3e15605ed7..f55b7f38a8 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -296,7 +296,7 @@ func TestManagerLockUnlock(t *testing.T) { require.NotNil(t, keyBlock) require.False(t, keyutils.IsEncryptedPEMBlock(keyBlock)) require.Len(t, keyBlock.Headers, 2) - currentDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil) + currentDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil, false) require.NoError(t, err) require.NotEmpty(t, currentDEK) @@ -349,7 +349,7 @@ func TestManagerLockUnlock(t *testing.T) { // a little bit, and is best effort only currentDEKString, ok := keyBlock.Headers[pemHeaderRaftDEK] require.True(t, ok) // there should never NOT be a current header - nowCurrentDEK, err := decodePEMHeaderValue(currentDEKString, unlockKeyResp.UnlockKey) + nowCurrentDEK, err := decodePEMHeaderValue(currentDEKString, unlockKeyResp.UnlockKey, false) require.NoError(t, err) // it should always be encrypted if bytes.Equal(currentDEK, nowCurrentDEK) { return fmt.Errorf("snapshot has not been finished yet") @@ -422,8 +422,13 @@ func TestManagerLockUnlock(t *testing.T) { return nil }, 1*time.Second)) - // the DEK should not have been rotated, just decrypted (which was tested previously) - unencryptedDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil) + // the new key should not be encrypted, and the DEK should also be unencrypted + // but not rotated + keyBlock, _ = pem.Decode(unlockedKey) + require.NotNil(t, keyBlock) + require.False(t, keyutils.IsEncryptedPEMBlock(keyBlock)) + + unencryptedDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil, false) require.NoError(t, err) require.NotNil(t, unencryptedDEK) require.Equal(t, currentDEK, unencryptedDEK) From 1194ebd9d8ea98352fac15382cf623f78d6f9345 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Tue, 3 Apr 2018 10:50:46 -0700 Subject: [PATCH 15/15] Propagate the FIPS boolean from node.go to the manager and hence to the raft storage layer. Also propagate it to the RaftDEKData objects in node.go and to the RaftDEKManager in the manager. Signed-off-by: Ying Li (cherry picked from commit ba11e512a26f2866ca34ecd887af31f77c472be6) --- integration/integration_test.go | 10 ++++++++-- manager/manager.go | 3 ++- manager/state/raft/raft.go | 3 +++ manager/state/raft/storage.go | 1 + node/node.go | 5 +++-- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index e1258e5ce7..5fc6ef0efb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -888,9 +888,15 @@ func TestMixedFIPSClusterNonMandatoryFIPS(t *testing.T) { pollClusterReady(t, cl, 2, 3) - // swap which nodes are fips and which are not - all should start up just fine + // switch which worker nodes are fips and which are not - all should start up just fine + // on managers, if we enable fips on a previously non-fips node, it won't be able to read + // non-fernet raft logs for nodeID, n := range cl.nodes { - n.config.FIPS = !n.config.FIPS + if n.IsManager() { + n.config.FIPS = false + } else { + n.config.FIPS = !n.config.FIPS + } require.NoError(t, n.Pause(false)) require.NoError(t, cl.StartNode(nodeID)) } diff --git a/manager/manager.go b/manager/manager.go index 3d68a2698a..2c6a0d3dcc 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -213,7 +213,7 @@ func New(config *Config) (*Manager, error) { raftCfg.HeartbeatTick = int(config.HeartbeatTick) } - dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter(), false) + dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter(), config.FIPS) if err != nil { return nil, err } @@ -227,6 +227,7 @@ func New(config *Config) (*Manager, error) { ForceNewCluster: config.ForceNewCluster, TLSCredentials: config.SecurityConfig.ClientTLSCreds, KeyRotator: dekRotator, + FIPS: config.FIPS, } raftNode := raft.NewNode(newNodeOpts) diff --git a/manager/state/raft/raft.go b/manager/state/raft/raft.go index 56b7c7c966..9eec8d4dfb 100644 --- a/manager/state/raft/raft.go +++ b/manager/state/raft/raft.go @@ -192,6 +192,9 @@ type NodeOptions struct { // DisableStackDump prevents Run from dumping goroutine stacks when the // store becomes stuck. DisableStackDump bool + + // FIPS specifies whether the raft encryption should be FIPS compliant + FIPS bool } func init() { diff --git a/manager/state/raft/storage.go b/manager/state/raft/storage.go index f538317c06..547b775645 100644 --- a/manager/state/raft/storage.go +++ b/manager/state/raft/storage.go @@ -34,6 +34,7 @@ func (n *Node) readFromDisk(ctx context.Context) (*raftpb.Snapshot, storage.WALD n.raftLogger = &storage.EncryptedRaftLogger{ StateDir: n.opts.StateDir, EncryptionKey: keys.CurrentDEK, + FIPS: n.opts.FIPS, } if keys.PendingDEK != nil { n.raftLogger.EncryptionKey = keys.PendingDEK diff --git a/node/node.go b/node/node.go index 8674f40f77..0886ea9659 100644 --- a/node/node.go +++ b/node/node.go @@ -695,7 +695,7 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP cancel func() error ) - krw := ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{}) + krw := ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{FIPS: n.config.FIPS}) // if FIPS is required, we want to make sure our key is stored in PKCS8 format if n.config.FIPS { krw.SetKeyFormatter(keyutils.FIPS) @@ -729,7 +729,7 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP if n.config.AutoLockManagers { n.unlockKey = encryption.GenerateSecretKey() } - krw = ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{}) + krw = ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{FIPS: n.config.FIPS}) rootCA, err = ca.CreateRootCA(ca.DefaultRootCN) if err != nil { return nil, nil, err @@ -898,6 +898,7 @@ func (n *Node) runManager(ctx context.Context, securityConfig *ca.SecurityConfig Availability: n.config.Availability, PluginGetter: n.config.PluginGetter, RootCAPaths: rootPaths, + FIPS: n.config.FIPS, }) if err != nil { return false, err