fix: add involves edge from task to agent:nomos at creation
Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
21
vendor/github.com/google/jsonschema-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/google/jsonschema-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 JSON Schema Go Project Authors
|
||||
|
||||
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.
|
||||
76
vendor/github.com/google/jsonschema-go/jsonschema/annotations.go
generated
vendored
Normal file
76
vendor/github.com/google/jsonschema-go/jsonschema/annotations.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import "maps"
|
||||
|
||||
// An annotations tracks certain properties computed by keywords that are used by validation.
|
||||
// ("Annotation" is the spec's term.)
|
||||
// In particular, the unevaluatedItems and unevaluatedProperties keywords need to know which
|
||||
// items and properties were evaluated (validated successfully).
|
||||
type annotations struct {
|
||||
allItems bool // all items were evaluated
|
||||
endIndex int // 1+largest index evaluated by prefixItems
|
||||
evaluatedIndexes map[int]bool // set of indexes evaluated by contains
|
||||
allProperties bool // all properties were evaluated
|
||||
evaluatedProperties map[string]bool // set of properties evaluated by various keywords
|
||||
}
|
||||
|
||||
// noteIndex marks i as evaluated.
|
||||
func (a *annotations) noteIndex(i int) {
|
||||
if a.evaluatedIndexes == nil {
|
||||
a.evaluatedIndexes = map[int]bool{}
|
||||
}
|
||||
a.evaluatedIndexes[i] = true
|
||||
}
|
||||
|
||||
// noteEndIndex marks items with index less than end as evaluated.
|
||||
func (a *annotations) noteEndIndex(end int) {
|
||||
if end > a.endIndex {
|
||||
a.endIndex = end
|
||||
}
|
||||
}
|
||||
|
||||
// noteProperty marks prop as evaluated.
|
||||
func (a *annotations) noteProperty(prop string) {
|
||||
if a.evaluatedProperties == nil {
|
||||
a.evaluatedProperties = map[string]bool{}
|
||||
}
|
||||
a.evaluatedProperties[prop] = true
|
||||
}
|
||||
|
||||
// noteProperties marks all the properties in props as evaluated.
|
||||
func (a *annotations) noteProperties(props map[string]bool) {
|
||||
a.evaluatedProperties = merge(a.evaluatedProperties, props)
|
||||
}
|
||||
|
||||
// merge adds b's annotations to a.
|
||||
// a must not be nil.
|
||||
func (a *annotations) merge(b *annotations) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
if b.allItems {
|
||||
a.allItems = true
|
||||
}
|
||||
if b.endIndex > a.endIndex {
|
||||
a.endIndex = b.endIndex
|
||||
}
|
||||
a.evaluatedIndexes = merge(a.evaluatedIndexes, b.evaluatedIndexes)
|
||||
if b.allProperties {
|
||||
a.allProperties = true
|
||||
}
|
||||
a.evaluatedProperties = merge(a.evaluatedProperties, b.evaluatedProperties)
|
||||
}
|
||||
|
||||
// merge adds t's keys to s and returns s.
|
||||
// If s is nil, it returns a copy of t.
|
||||
func merge[K comparable](s, t map[K]bool) map[K]bool {
|
||||
if s == nil {
|
||||
return maps.Clone(t)
|
||||
}
|
||||
maps.Copy(s, t)
|
||||
return s
|
||||
}
|
||||
115
vendor/github.com/google/jsonschema-go/jsonschema/doc.go
generated
vendored
Normal file
115
vendor/github.com/google/jsonschema-go/jsonschema/doc.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package jsonschema is an implementation of the [JSON Schema specification],
|
||||
a JSON-based format for describing the structure of JSON data.
|
||||
The package can be used to read schemas for code generation, and to validate
|
||||
data using the draft 2020-12 and draft-07 specifications. Validation with
|
||||
other drafts or custom meta-schemas is not supported.
|
||||
|
||||
Construct a [Schema] as you would any Go struct (for example, by writing
|
||||
a struct literal), or unmarshal a JSON schema into a [Schema] in the usual
|
||||
way (with [encoding/json], for instance). It can then be used for code
|
||||
generation or other purposes without further processing.
|
||||
You can also infer a schema from a Go struct.
|
||||
|
||||
# Resolution
|
||||
|
||||
A Schema can refer to other schemas, both inside and outside itself. These
|
||||
references must be resolved before a schema can be used for validation.
|
||||
Call [Schema.Resolve] to obtain a resolved schema (called a [Resolved]).
|
||||
If the schema has external references, pass a [ResolveOptions] with a [Loader]
|
||||
to load them. To validate default values in a schema, set
|
||||
[ResolveOptions.ValidateDefaults] to true.
|
||||
|
||||
# Validation
|
||||
|
||||
Call [Resolved.Validate] to validate a JSON value. The value must be a
|
||||
Go value that looks like the result of unmarshaling a JSON value into an
|
||||
[any] or a struct. For example, the JSON value
|
||||
|
||||
{"name": "Al", "scores": [90, 80, 100]}
|
||||
|
||||
could be represented as the Go value
|
||||
|
||||
map[string]any{
|
||||
"name": "Al",
|
||||
"scores": []any{90, 80, 100},
|
||||
}
|
||||
|
||||
or as a value of this type:
|
||||
|
||||
type Player struct {
|
||||
Name string `json:"name"`
|
||||
Scores []int `json:"scores"`
|
||||
}
|
||||
|
||||
# Inference
|
||||
|
||||
The [For] function returns a [Schema] describing the given Go type.
|
||||
Each field in the struct becomes a property of the schema.
|
||||
The values of "json" tags are respected: the field's property name is taken
|
||||
from the tag, and fields omitted from the JSON are omitted from the schema as
|
||||
well.
|
||||
For example, `jsonschema.For[Player]()` returns this schema:
|
||||
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"scores": {
|
||||
"type": "array",
|
||||
"items": {"type": "integer"}
|
||||
}
|
||||
"required": ["name", "scores"],
|
||||
"additionalProperties": {"not": {}}
|
||||
}
|
||||
}
|
||||
|
||||
Use the "jsonschema" struct tag to provide a description for the property:
|
||||
|
||||
type Player struct {
|
||||
Name string `json:"name" jsonschema:"player name"`
|
||||
Scores []int `json:"scores" jsonschema:"scores of player's games"`
|
||||
}
|
||||
|
||||
# Deviations from the specification
|
||||
|
||||
Regular expressions are processed with Go's regexp package, which differs
|
||||
from ECMA 262, most significantly in not supporting back-references.
|
||||
See [this table of differences] for more.
|
||||
|
||||
The "format" keyword described in [section 7 of the validation spec] is recorded
|
||||
in the Schema, but is ignored during validation.
|
||||
It does not even produce [annotations].
|
||||
Use the "pattern" keyword instead: it will work more reliably across JSON Schema
|
||||
implementations. See [learnjsonschema.com] for more recommendations about "format".
|
||||
|
||||
The content keywords described in [section 8 of the validation spec]
|
||||
are recorded in the schema, but ignored during validation.
|
||||
|
||||
# Controlling behavior changes
|
||||
|
||||
Minor and patch releases of this package may introduce behavior changes as part
|
||||
of bug fixes or correctness improvements. To help manage the impact of such
|
||||
changes, the package allows you to access previous behaviors using the
|
||||
`JSONSCHEMAGODEBUG` environment variable. The available settings are listed
|
||||
below; additional options may be introduced in future releases.
|
||||
|
||||
- **typeschemasnull**: When set to `"1"`, the inferred schema for slices will
|
||||
*not* include the `null` type alongside the array type. It will also avoid
|
||||
adding `null` to non-native pointer types (such as `time.Time`). This restores
|
||||
the behavior from versions prior to v0.3.0. The default behavior is to include
|
||||
`null` in these cases.
|
||||
|
||||
[JSON Schema specification]: https://json-schema.org
|
||||
[section 7 of the validation spec]: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
|
||||
[section 8 of the validation spec]: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8
|
||||
[learnjsonschema.com]: https://www.learnjsonschema.com/2020-12/format-annotation/format/
|
||||
[this table of differences]: https://github.com/dlclark/regexp2?tab=readme-ov-file#compare-regexp-and-regexp2
|
||||
[annotations]: https://json-schema.org/draft/2020-12/json-schema-core#name-annotations
|
||||
*/
|
||||
package jsonschema
|
||||
399
vendor/github.com/google/jsonschema-go/jsonschema/infer.go
generated
vendored
Normal file
399
vendor/github.com/google/jsonschema-go/jsonschema/infer.go
generated
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains functions that infer a schema from a Go type.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
const debugEnv = "JSONSCHEMAGODEBUG"
|
||||
|
||||
// ForOptions are options for the [For] and [ForType] functions.
|
||||
type ForOptions struct {
|
||||
// If IgnoreInvalidTypes is true, fields that can't be represented as a JSON
|
||||
// Schema are ignored instead of causing an error.
|
||||
// This allows callers to adjust the resulting schema using custom knowledge.
|
||||
// For example, an interface type where all the possible implementations are
|
||||
// known can be described with "oneof".
|
||||
IgnoreInvalidTypes bool
|
||||
|
||||
// TypeSchemas maps types to their schemas.
|
||||
// If [For] encounters a type that is a key in this map, the
|
||||
// corresponding value is used as the resulting schema (after cloning to
|
||||
// ensure uniqueness).
|
||||
// Types in this map override the default translations, as described
|
||||
// in [For]'s documentation.
|
||||
// PropertyOrder defined in these schemas will not be used in [For] or [ForType].
|
||||
TypeSchemas map[reflect.Type]*Schema
|
||||
}
|
||||
|
||||
// For constructs a JSON schema object for the given type argument.
|
||||
// If non-nil, the provided options configure certain aspects of this contruction,
|
||||
// described below.
|
||||
|
||||
// It translates Go types into compatible JSON schema types, as follows.
|
||||
// These defaults can be overridden by [ForOptions.TypeSchemas].
|
||||
//
|
||||
// - Strings have schema type "string".
|
||||
// - Bools have schema type "boolean".
|
||||
// - Signed and unsigned integer types have schema type "integer".
|
||||
// - Floating point types have schema type "number".
|
||||
// - Slices and arrays have schema type "array", and a corresponding schema
|
||||
// for items.
|
||||
// - Maps with string key have schema type "object", and corresponding
|
||||
// schema for additionalProperties.
|
||||
// - Structs have schema type "object", and disallow additionalProperties.
|
||||
// Their properties are derived from exported struct fields, using the
|
||||
// struct field JSON name. Fields that are marked "omitempty" or "omitzero" are
|
||||
// considered optional; all other fields become required properties.
|
||||
// For structs, the PropertyOrder will be set to the field order.
|
||||
// - Some types in the standard library that implement json.Marshaler
|
||||
// translate to schemas that match the values to which they marshal.
|
||||
// For example, [time.Time] translates to the schema for strings.
|
||||
//
|
||||
// For will return an error if there is a cycle in the types.
|
||||
//
|
||||
// By default, For returns an error if t contains (possibly recursively) any of the
|
||||
// following Go types, as they are incompatible with the JSON schema spec.
|
||||
// If [ForOptions.IgnoreInvalidTypes] is true, then these types are ignored instead.
|
||||
// - maps with key other than 'string'
|
||||
// - function types
|
||||
// - channel types
|
||||
// - complex numbers
|
||||
// - unsafe pointers
|
||||
//
|
||||
// This function recognizes struct field tags named "jsonschema".
|
||||
// A jsonschema tag on a field is used as the description for the corresponding property.
|
||||
// For future compatibility, descriptions must not start with "WORD=", where WORD is a
|
||||
// sequence of non-whitespace characters.
|
||||
func For[T any](opts *ForOptions) (*Schema, error) {
|
||||
if opts == nil {
|
||||
opts = &ForOptions{}
|
||||
}
|
||||
schemas := maps.Clone(initialSchemaMap)
|
||||
// Add types from the options. They override the default ones.
|
||||
maps.Copy(schemas, opts.TypeSchemas)
|
||||
s, err := forType(reflect.TypeFor[T](), map[reflect.Type]bool{}, opts.IgnoreInvalidTypes, schemas)
|
||||
if err != nil {
|
||||
var z T
|
||||
return nil, fmt.Errorf("For[%T](): %w", z, err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ForType is like [For], but takes a [reflect.Type]
|
||||
func ForType(t reflect.Type, opts *ForOptions) (*Schema, error) {
|
||||
if opts == nil {
|
||||
opts = &ForOptions{}
|
||||
}
|
||||
schemas := maps.Clone(initialSchemaMap)
|
||||
// Add types from the options. They override the default ones.
|
||||
maps.Copy(schemas, opts.TypeSchemas)
|
||||
s, err := forType(t, map[reflect.Type]bool{}, opts.IgnoreInvalidTypes, schemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ForType(%s): %w", t, err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Helper to create a *float64 pointer from a value
|
||||
func f64Ptr(f float64) *float64 {
|
||||
return &f
|
||||
}
|
||||
|
||||
func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas map[reflect.Type]*Schema) (*Schema, error) {
|
||||
// Follow pointers: the schema for *T is almost the same as for T, except that
|
||||
// an explicit JSON "null" is allowed for the pointer.
|
||||
allowNull := false
|
||||
for t.Kind() == reflect.Pointer {
|
||||
allowNull = true
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
// Check for cycles
|
||||
// User defined types have a name, so we can skip those that are natively defined
|
||||
if t.Name() != "" {
|
||||
if seen[t] {
|
||||
return nil, fmt.Errorf("cycle detected for type %v", t)
|
||||
}
|
||||
seen[t] = true
|
||||
defer delete(seen, t)
|
||||
}
|
||||
|
||||
if s := schemas[t]; s != nil {
|
||||
cloned := s.CloneSchemas()
|
||||
if os.Getenv(debugEnv) != "typeschemasnull=1" && allowNull {
|
||||
if cloned.Type != "" {
|
||||
cloned.Types = []string{"null", cloned.Type}
|
||||
cloned.Type = ""
|
||||
} else if !slices.Contains(cloned.Types, "null") {
|
||||
cloned.Types = append([]string{"null"}, cloned.Types...)
|
||||
}
|
||||
}
|
||||
return cloned, nil
|
||||
}
|
||||
|
||||
var (
|
||||
s = new(Schema)
|
||||
err error
|
||||
)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
s.Type = "boolean"
|
||||
|
||||
case reflect.Int, reflect.Int64:
|
||||
s.Type = "integer"
|
||||
|
||||
case reflect.Uint, reflect.Uint64, reflect.Uintptr:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(0)
|
||||
|
||||
case reflect.Int8:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(math.MinInt8)
|
||||
s.Maximum = f64Ptr(math.MaxInt8)
|
||||
|
||||
case reflect.Uint8:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(0)
|
||||
s.Maximum = f64Ptr(math.MaxUint8)
|
||||
|
||||
case reflect.Int16:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(math.MinInt16)
|
||||
s.Maximum = f64Ptr(math.MaxInt16)
|
||||
|
||||
case reflect.Uint16:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(0)
|
||||
s.Maximum = f64Ptr(math.MaxUint16)
|
||||
|
||||
case reflect.Int32:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(math.MinInt32)
|
||||
s.Maximum = f64Ptr(math.MaxInt32)
|
||||
|
||||
case reflect.Uint32:
|
||||
s.Type = "integer"
|
||||
s.Minimum = f64Ptr(0)
|
||||
s.Maximum = f64Ptr(math.MaxUint32)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s.Type = "number"
|
||||
|
||||
case reflect.Interface:
|
||||
// Unrestricted
|
||||
|
||||
case reflect.Map:
|
||||
if t.Key().Kind() != reflect.String && !t.Key().Implements(reflect.TypeFor[encoding.TextMarshaler]()) {
|
||||
if ignore {
|
||||
return nil, nil // ignore
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported map key type %v", t.Key().Kind())
|
||||
}
|
||||
s.Type = "object"
|
||||
s.AdditionalProperties, err = forType(t.Elem(), seen, ignore, schemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("computing map value schema: %v", err)
|
||||
}
|
||||
if ignore && s.AdditionalProperties == nil {
|
||||
// Ignore if the element type is invalid.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
if os.Getenv(debugEnv) != "typeschemasnull=1" && t.Kind() == reflect.Slice {
|
||||
s.Types = []string{"null", "array"}
|
||||
} else {
|
||||
s.Type = "array"
|
||||
}
|
||||
itemsSchema, err := forType(t.Elem(), seen, ignore, schemas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("computing element schema: %v", err)
|
||||
}
|
||||
if itemsSchema == nil {
|
||||
return nil, nil
|
||||
}
|
||||
s.Items = itemsSchema
|
||||
if ignore && s.Items == nil {
|
||||
// Ignore if the element type is invalid.
|
||||
return nil, nil
|
||||
}
|
||||
if t.Kind() == reflect.Array {
|
||||
s.MinItems = Ptr(t.Len())
|
||||
s.MaxItems = Ptr(t.Len())
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
s.Type = "string"
|
||||
|
||||
case reflect.Struct:
|
||||
s.Type = "object"
|
||||
// no additional properties are allowed
|
||||
s.AdditionalProperties = falseSchema()
|
||||
|
||||
// If skipPath is non-nil, it is path to an anonymous field whose
|
||||
// schema has been replaced by a known schema.
|
||||
var skipPath []int
|
||||
for _, field := range reflect.VisibleFields(t) {
|
||||
if s.Properties == nil {
|
||||
s.Properties = make(map[string]*Schema)
|
||||
}
|
||||
if field.Anonymous {
|
||||
override := schemas[field.Type]
|
||||
if override != nil {
|
||||
// Type must be object, and only properties can be set.
|
||||
if override.Type != "object" {
|
||||
return nil, fmt.Errorf(`custom schema for embedded struct must have type "object", got %q`,
|
||||
override.Type)
|
||||
}
|
||||
// Check that all keywords relevant for objects are absent, except properties.
|
||||
ov := reflect.ValueOf(override).Elem()
|
||||
for _, sfi := range schemaFieldInfos {
|
||||
if sfi.sf.Name == "Type" || sfi.sf.Name == "Properties" {
|
||||
continue
|
||||
}
|
||||
fv := ov.FieldByIndex(sfi.sf.Index)
|
||||
if !fv.IsZero() {
|
||||
return nil, fmt.Errorf(`overrides for embedded fields can have only "Type" and "Properties"; this has %q`, sfi.sf.Name)
|
||||
}
|
||||
}
|
||||
|
||||
skipPath = field.Index
|
||||
keys := make([]string, 0, len(override.Properties))
|
||||
for k := range override.Properties {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
for _, name := range keys {
|
||||
if _, ok := s.Properties[name]; !ok {
|
||||
s.Properties[name] = override.Properties[name].CloneSchemas()
|
||||
s.PropertyOrder = append(s.PropertyOrder, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check to see if this field has been promoted from a replaced anonymous
|
||||
// type.
|
||||
if skipPath != nil {
|
||||
skip := false
|
||||
if len(field.Index) >= len(skipPath) {
|
||||
skip = true
|
||||
for i, index := range skipPath {
|
||||
if field.Index[i] != index {
|
||||
// If we're no longer in a subfield.
|
||||
skip = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
} else {
|
||||
// Anonymous fields are followed immediately by their promoted fields.
|
||||
// Once we encounter a field that *isn't* promoted, we can stop
|
||||
// checking.
|
||||
skipPath = nil
|
||||
}
|
||||
}
|
||||
|
||||
info := fieldJSONInfo(field)
|
||||
if info.omit {
|
||||
continue
|
||||
}
|
||||
fs, err := forType(field.Type, seen, ignore, schemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ignore && fs == nil {
|
||||
// Skip fields of invalid type.
|
||||
continue
|
||||
}
|
||||
if tag, ok := field.Tag.Lookup("jsonschema"); ok {
|
||||
if tag == "" {
|
||||
return nil, fmt.Errorf("empty jsonschema tag on struct field %s.%s", t, field.Name)
|
||||
}
|
||||
if disallowedPrefixRegexp.MatchString(tag) {
|
||||
return nil, fmt.Errorf("tag must not begin with 'WORD=': %q", tag)
|
||||
}
|
||||
fs.Description = tag
|
||||
}
|
||||
s.Properties[info.name] = fs
|
||||
|
||||
s.PropertyOrder = append(s.PropertyOrder, info.name)
|
||||
|
||||
if !info.settings["omitempty"] && !info.settings["omitzero"] {
|
||||
s.Required = append(s.Required, info.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove PropertyOrder duplicates, keeping the last occurrence
|
||||
if len(s.PropertyOrder) > 1 {
|
||||
seen := make(map[string]bool)
|
||||
// Create a slice to hold the cleaned order (capacity = current length)
|
||||
cleaned := make([]string, 0, len(s.PropertyOrder))
|
||||
|
||||
// Iterate backwards
|
||||
for i := len(s.PropertyOrder) - 1; i >= 0; i-- {
|
||||
name := s.PropertyOrder[i]
|
||||
if !seen[name] {
|
||||
cleaned = append(cleaned, name)
|
||||
seen[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Since we collected them backwards, we need to reverse the result
|
||||
// to restore the correct order.
|
||||
slices.Reverse(cleaned)
|
||||
s.PropertyOrder = cleaned
|
||||
}
|
||||
|
||||
default:
|
||||
if ignore {
|
||||
// Ignore.
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("type %v is unsupported by jsonschema", t)
|
||||
}
|
||||
if allowNull && s.Type != "" {
|
||||
s.Types = []string{"null", s.Type}
|
||||
s.Type = ""
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// initialSchemaMap holds types from the standard library that have MarshalJSON methods.
|
||||
var initialSchemaMap = make(map[reflect.Type]*Schema)
|
||||
|
||||
func init() {
|
||||
ss := &Schema{Type: "string"}
|
||||
initialSchemaMap[reflect.TypeFor[time.Time]()] = ss
|
||||
initialSchemaMap[reflect.TypeFor[slog.Level]()] = ss
|
||||
if os.Getenv(debugEnv) == "typeschemasnull=1" {
|
||||
initialSchemaMap[reflect.TypeFor[big.Int]()] = &Schema{Types: []string{"null", "string"}}
|
||||
} else {
|
||||
initialSchemaMap[reflect.TypeFor[big.Int]()] = ss
|
||||
}
|
||||
initialSchemaMap[reflect.TypeFor[big.Rat]()] = ss
|
||||
initialSchemaMap[reflect.TypeFor[big.Float]()] = ss
|
||||
}
|
||||
|
||||
// Disallow jsonschema tag values beginning "WORD=", for future expansion.
|
||||
var disallowedPrefixRegexp = regexp.MustCompile("^[^ \t\n]*=")
|
||||
160
vendor/github.com/google/jsonschema-go/jsonschema/json_pointer.go
generated
vendored
Normal file
160
vendor/github.com/google/jsonschema-go/jsonschema/json_pointer.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements JSON Pointers.
|
||||
// A JSON Pointer is a path that refers to one JSON value within another.
|
||||
// If the path is empty, it refers to the root value.
|
||||
// Otherwise, it is a sequence of slash-prefixed strings, like "/points/1/x",
|
||||
// selecting successive properties (for JSON objects) or items (for JSON arrays).
|
||||
// For example, when applied to this JSON value:
|
||||
// {
|
||||
// "points": [
|
||||
// {"x": 1, "y": 2},
|
||||
// {"x": 3, "y": 4}
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// the JSON Pointer "/points/1/x" refers to the number 3.
|
||||
// See the spec at https://datatracker.ietf.org/doc/html/rfc6901.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
jsonPointerEscaper = strings.NewReplacer("~", "~0", "/", "~1")
|
||||
jsonPointerUnescaper = strings.NewReplacer("~0", "~", "~1", "/")
|
||||
)
|
||||
|
||||
func escapeJSONPointerSegment(s string) string {
|
||||
return jsonPointerEscaper.Replace(s)
|
||||
}
|
||||
|
||||
func unescapeJSONPointerSegment(s string) string {
|
||||
return jsonPointerUnescaper.Replace(s)
|
||||
}
|
||||
|
||||
// parseJSONPointer splits a JSON Pointer into a sequence of segments. It doesn't
|
||||
// convert strings to numbers, because that depends on the traversal: a segment
|
||||
// is treated as a number when applied to an array, but a string when applied to
|
||||
// an object. See section 4 of the spec.
|
||||
func parseJSONPointer(ptr string) (segments []string, err error) {
|
||||
if ptr == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if ptr[0] != '/' {
|
||||
return nil, fmt.Errorf("JSON Pointer %q does not begin with '/'", ptr)
|
||||
}
|
||||
// Unlike file paths, consecutive slashes are not coalesced.
|
||||
// Split is nicer than Cut here, because it gets a final "/" right.
|
||||
segments = strings.Split(ptr[1:], "/")
|
||||
if strings.Contains(ptr, "~") {
|
||||
// Undo the simple escaping rules that allow one to include a slash in a segment.
|
||||
for i := range segments {
|
||||
segments[i] = unescapeJSONPointerSegment(segments[i])
|
||||
}
|
||||
}
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
// dereferenceJSONPointer returns the Schema that sptr points to within s,
|
||||
// or an error if none.
|
||||
// This implementation suffices for JSON Schema: pointers are applied only to Schemas,
|
||||
// and refer only to Schemas.
|
||||
func dereferenceJSONPointer(s *Schema, sptr string) (_ *Schema, err error) {
|
||||
defer wrapf(&err, "JSON Pointer %q", sptr)
|
||||
|
||||
segments, err := parseJSONPointer(sptr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := reflect.ValueOf(s)
|
||||
for _, seg := range segments {
|
||||
switch v.Kind() {
|
||||
case reflect.Pointer:
|
||||
v = v.Elem()
|
||||
if !v.IsValid() {
|
||||
return nil, errors.New("navigated to nil reference")
|
||||
}
|
||||
fallthrough // if valid, can only be a pointer to a Schema
|
||||
|
||||
case reflect.Struct:
|
||||
// The segment must refer to a field in a Schema.
|
||||
if v.Type() != reflect.TypeFor[Schema]() {
|
||||
return nil, fmt.Errorf("navigated to non-Schema %s", v.Type())
|
||||
}
|
||||
v = lookupSchemaField(v, seg)
|
||||
if !v.IsValid() {
|
||||
return nil, fmt.Errorf("no schema field %q", seg)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
// The segment must be an integer without leading zeroes that refers to an item in the
|
||||
// slice or array.
|
||||
if seg == "-" {
|
||||
return nil, errors.New("the JSON Pointer array segment '-' is not supported")
|
||||
}
|
||||
if len(seg) > 1 && seg[0] == '0' {
|
||||
return nil, fmt.Errorf("segment %q has leading zeroes", seg)
|
||||
}
|
||||
n, err := strconv.Atoi(seg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid int: %q", seg)
|
||||
}
|
||||
if n < 0 || n >= v.Len() {
|
||||
return nil, fmt.Errorf("index %d is out of bounds for array of length %d", n, v.Len())
|
||||
}
|
||||
v = v.Index(n)
|
||||
// Cannot be invalid.
|
||||
case reflect.Map:
|
||||
// The segment must be a key in the map.
|
||||
v = v.MapIndex(reflect.ValueOf(seg))
|
||||
if !v.IsValid() {
|
||||
return nil, fmt.Errorf("no key %q in map", seg)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("value %s (%s) is not a schema, slice or map", v, v.Type())
|
||||
}
|
||||
}
|
||||
if s, ok := v.Interface().(*Schema); ok {
|
||||
return s, nil
|
||||
}
|
||||
return nil, fmt.Errorf("does not refer to a schema, but to a %s", v.Type())
|
||||
}
|
||||
|
||||
// lookupSchemaField returns the value of the field with the given name in v,
|
||||
// or the zero value if there is no such field or it is not of type Schema or *Schema.
|
||||
func lookupSchemaField(v reflect.Value, name string) reflect.Value {
|
||||
if name == "type" {
|
||||
// The "type" keyword may refer to Type or Types.
|
||||
// At most one will be non-zero.
|
||||
if t := v.FieldByName("Type"); !t.IsZero() {
|
||||
return t
|
||||
}
|
||||
return v.FieldByName("Types")
|
||||
}
|
||||
if name == "items" {
|
||||
// The "items" keyword refers to the "union type" that is either a schema or a schema array.
|
||||
// Implemented using the Items representing the schema and ItemsArray for the schema array.
|
||||
if items := v.FieldByName("Items"); items.IsValid() && !items.IsNil() {
|
||||
return items
|
||||
}
|
||||
return v.FieldByName("ItemsArray")
|
||||
}
|
||||
if name == "dependencies" {
|
||||
// The "dependencies" keyword refers to both DependencyStrings and DependencySchemas maps.
|
||||
// The value on schemaFieldMap is not garanteed to be DependencySchemas which we want
|
||||
// for pointer dereference. So we use FieldByName to get the DependencySchemas map.
|
||||
return v.FieldByName("DependencySchemas")
|
||||
}
|
||||
if sf, ok := schemaFieldMap[name]; ok {
|
||||
return v.FieldByIndex(sf.Index)
|
||||
}
|
||||
return reflect.Value{}
|
||||
}
|
||||
589
vendor/github.com/google/jsonschema-go/jsonschema/resolve.go
generated
vendored
Normal file
589
vendor/github.com/google/jsonschema-go/jsonschema/resolve.go
generated
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file deals with preparing a schema for validation, including various checks,
|
||||
// optimizations, and the resolution of cross-schema references.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Resolved consists of a [Schema] along with associated information needed to
|
||||
// validate documents against it.
|
||||
// A Resolved has been validated against its meta-schema, and all its references
|
||||
// (the $ref and $dynamicRef keywords) have been resolved to their referenced Schemas.
|
||||
// Call [Schema.Resolve] to obtain a Resolved from a Schema.
|
||||
type Resolved struct {
|
||||
root *Schema
|
||||
draft draft
|
||||
// map from $ids to their schemas
|
||||
resolvedURIs map[string]*Schema
|
||||
// map from schemas to additional info computed during resolution
|
||||
resolvedInfos map[*Schema]*resolvedInfo
|
||||
}
|
||||
|
||||
type draft int
|
||||
|
||||
const (
|
||||
draft7 = iota
|
||||
draft2020
|
||||
)
|
||||
|
||||
func newResolved(s *Schema) *Resolved {
|
||||
return &Resolved{
|
||||
root: s,
|
||||
draft: detectDraft(s),
|
||||
resolvedURIs: map[string]*Schema{},
|
||||
resolvedInfos: map[*Schema]*resolvedInfo{},
|
||||
}
|
||||
}
|
||||
|
||||
// detectDraft inspects the raw JSON to determine the schema version.
|
||||
func detectDraft(s *Schema) draft {
|
||||
// Check explicit $schema declaration
|
||||
switch s.Schema {
|
||||
case draft7SchemaVersion, draft7SecSchemaVersion:
|
||||
return draft7
|
||||
case draft202012SchemaVersion:
|
||||
return draft2020
|
||||
default:
|
||||
// If nothing matches default to the latest supported version.
|
||||
return draft2020
|
||||
}
|
||||
}
|
||||
|
||||
// resolvedInfo holds information specific to a schema that is computed by [Schema.Resolve].
|
||||
type resolvedInfo struct {
|
||||
s *Schema
|
||||
// The JSON Pointer path from the root schema to here.
|
||||
// Used in errors.
|
||||
path string
|
||||
// The schema's base schema.
|
||||
// If the schema is the root or has an ID, its base is itself.
|
||||
// Otherwise, its base is the innermost enclosing schema whose base
|
||||
// is itself.
|
||||
// Intuitively, a base schema is one that can be referred to with a
|
||||
// fragmentless URI.
|
||||
base *Schema
|
||||
// The URI for the schema, if it is the root or has an ID.
|
||||
// Otherwise nil.
|
||||
// Invariants:
|
||||
// s.base.uri != nil.
|
||||
// s.base == s <=> s.uri != nil
|
||||
uri *url.URL
|
||||
// The schema to which Ref refers.
|
||||
resolvedRef *Schema
|
||||
|
||||
// If the schema has a dynamic ref, exactly one of the next two fields
|
||||
// will be non-zero after successful resolution.
|
||||
// The schema to which the dynamic ref refers when it acts lexically.
|
||||
resolvedDynamicRef *Schema
|
||||
// The anchor to look up on the stack when the dynamic ref acts dynamically.
|
||||
dynamicRefAnchor string
|
||||
|
||||
// The following fields are independent of arguments to Schema.Resolved,
|
||||
// so they could live on the Schema. We put them here for simplicity.
|
||||
|
||||
// The set of required properties.
|
||||
isRequired map[string]bool
|
||||
|
||||
// Compiled regexps.
|
||||
pattern *regexp.Regexp
|
||||
patternProperties map[*regexp.Regexp]*Schema
|
||||
|
||||
// Map from anchors to subschemas.
|
||||
anchors map[string]anchorInfo
|
||||
}
|
||||
|
||||
// Schema returns the schema that was resolved.
|
||||
// It must not be modified.
|
||||
func (r *Resolved) Schema() *Schema { return r.root }
|
||||
|
||||
// schemaString returns a short string describing the schema.
|
||||
func (r *Resolved) schemaString(s *Schema) string {
|
||||
if s.ID != "" {
|
||||
return s.ID
|
||||
}
|
||||
info := r.resolvedInfos[s]
|
||||
if info.path != "" {
|
||||
return info.path
|
||||
}
|
||||
return "<anonymous schema>"
|
||||
}
|
||||
|
||||
// A Loader reads and unmarshals the schema at uri, if any.
|
||||
type Loader func(uri *url.URL) (*Schema, error)
|
||||
|
||||
// ResolveOptions are options for [Schema.Resolve].
|
||||
type ResolveOptions struct {
|
||||
// BaseURI is the URI relative to which the root schema should be resolved.
|
||||
// If non-empty, must be an absolute URI (one that starts with a scheme).
|
||||
// It is resolved (in the URI sense; see [url.ResolveReference]) with root's
|
||||
// $id property.
|
||||
// If the resulting URI is not absolute, then the schema cannot contain
|
||||
// relative URI references.
|
||||
BaseURI string
|
||||
// Loader loads schemas that are referred to by a $ref but are not under the
|
||||
// root schema (remote references).
|
||||
// If nil, resolving a remote reference will return an error.
|
||||
Loader Loader
|
||||
// ValidateDefaults determines whether to validate values of "default" keywords
|
||||
// against their schemas.
|
||||
// The [JSON Schema specification] does not require this, but it is recommended
|
||||
// if defaults will be used.
|
||||
//
|
||||
// [JSON Schema specification]: https://json-schema.org/understanding-json-schema/reference/annotations
|
||||
ValidateDefaults bool
|
||||
}
|
||||
|
||||
// Resolve resolves all references within the schema and performs other tasks that
|
||||
// prepare the schema for validation.
|
||||
// If opts is nil, the default values are used.
|
||||
// The schema must not be changed after Resolve is called.
|
||||
// The same schema may be resolved multiple times.
|
||||
func (root *Schema) Resolve(opts *ResolveOptions) (*Resolved, error) {
|
||||
// There are up to five steps required to prepare a schema to validate.
|
||||
// 1. Load: read the schema from somewhere and unmarshal it.
|
||||
// This schema (root) may have been loaded or created in memory, but other schemas that
|
||||
// come into the picture in step 4 will be loaded by the given loader.
|
||||
// 2. Check: validate the schema against a meta-schema, and perform other well-formedness checks.
|
||||
// Precompute some values along the way.
|
||||
// 3. Resolve URIs: determine the base URI of the root and all its subschemas, and
|
||||
// resolve (in the URI sense) all identifiers and anchors with their bases. This step results
|
||||
// in a map from URIs to schemas within root.
|
||||
// 4. Resolve references: all refs in the schemas are replaced with the schema they refer to.
|
||||
// 5. (Optional.) If opts.ValidateDefaults is true, validate the defaults.
|
||||
r := &resolver{loaded: map[string]*Resolved{}}
|
||||
if opts != nil {
|
||||
r.opts = *opts
|
||||
}
|
||||
var base *url.URL
|
||||
if r.opts.BaseURI == "" {
|
||||
base = &url.URL{} // so we can call ResolveReference on it
|
||||
} else {
|
||||
var err error
|
||||
base, err = url.Parse(r.opts.BaseURI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing base URI: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.opts.Loader == nil {
|
||||
r.opts.Loader = func(uri *url.URL) (*Schema, error) {
|
||||
return nil, errors.New("cannot resolve remote schemas: no loader passed to Schema.Resolve")
|
||||
}
|
||||
}
|
||||
|
||||
resolved, err := r.resolve(root, base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.opts.ValidateDefaults {
|
||||
if err := resolved.validateDefaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// TODO: before we return, throw away anything we don't need for validation.
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// A resolver holds the state for resolution.
|
||||
type resolver struct {
|
||||
opts ResolveOptions
|
||||
// A cache of loaded and partly resolved schemas. (They may not have had their
|
||||
// refs resolved.) The cache ensures that the loader will never be called more
|
||||
// than once with the same URI, and that reference cycles are handled properly.
|
||||
loaded map[string]*Resolved
|
||||
}
|
||||
|
||||
func (r *resolver) resolve(s *Schema, baseURI *url.URL) (*Resolved, error) {
|
||||
if baseURI.Fragment != "" {
|
||||
return nil, fmt.Errorf("base URI %s must not have a fragment", baseURI)
|
||||
}
|
||||
rs := newResolved(s)
|
||||
|
||||
if err := s.check(rs.resolvedInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := resolveURIs(rs, baseURI); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remember the schema by both the URI we loaded it from and its canonical name,
|
||||
// which may differ if the schema has an $id.
|
||||
// We must set the map before calling resolveRefs, or ref cycles will cause unbounded recursion.
|
||||
r.loaded[baseURI.String()] = rs
|
||||
r.loaded[rs.resolvedInfos[s].uri.String()] = rs
|
||||
|
||||
if err := r.resolveRefs(rs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (root *Schema) check(infos map[*Schema]*resolvedInfo) error {
|
||||
// Check for structural validity. Do this first and fail fast:
|
||||
// bad structure will cause other code to panic.
|
||||
if err := root.checkStructure(infos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
report := func(err error) { errs = append(errs, err) }
|
||||
|
||||
for ss := range root.all() {
|
||||
ss.checkLocal(report, infos)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// checkStructure verifies that root and its subschemas form a tree.
|
||||
// It also assigns each schema a unique path, to improve error messages.
|
||||
func (root *Schema) checkStructure(infos map[*Schema]*resolvedInfo) error {
|
||||
assert(len(infos) == 0, "non-empty infos")
|
||||
|
||||
var check func(reflect.Value, []byte) error
|
||||
check = func(v reflect.Value, path []byte) error {
|
||||
// For the purpose of error messages, the root schema has path "root"
|
||||
// and other schemas' paths are their JSON Pointer from the root.
|
||||
p := "root"
|
||||
if len(path) > 0 {
|
||||
p = string(path)
|
||||
}
|
||||
s := v.Interface().(*Schema)
|
||||
if s == nil {
|
||||
return fmt.Errorf("jsonschema: schema at %s is nil", p)
|
||||
}
|
||||
if info, ok := infos[s]; ok {
|
||||
// We've seen s before.
|
||||
// The schema graph at root is not a tree, but it needs to
|
||||
// be because a schema's base must be unique.
|
||||
// A cycle would also put Schema.all into an infinite recursion.
|
||||
return fmt.Errorf("jsonschema: schemas at %s do not form a tree; %s appears more than once (also at %s)",
|
||||
root, info.path, p)
|
||||
}
|
||||
infos[s] = &resolvedInfo{s: s, path: p}
|
||||
|
||||
for _, info := range schemaFieldInfos {
|
||||
fv := v.Elem().FieldByIndex(info.sf.Index)
|
||||
switch info.sf.Type {
|
||||
case schemaType:
|
||||
// A field that contains an individual schema.
|
||||
// A nil is valid: it just means the field isn't present.
|
||||
if !fv.IsNil() {
|
||||
if err := check(fv, fmt.Appendf(path, "/%s", info.jsonName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case schemaSliceType:
|
||||
for i := range fv.Len() {
|
||||
if err := check(fv.Index(i), fmt.Appendf(path, "/%s/%d", info.jsonName, i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case schemaMapType:
|
||||
iter := fv.MapRange()
|
||||
for iter.Next() {
|
||||
key := escapeJSONPointerSegment(iter.Key().String())
|
||||
if err := check(iter.Value(), fmt.Appendf(path, "/%s/%s", info.jsonName, key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return check(reflect.ValueOf(root), make([]byte, 0, 256))
|
||||
}
|
||||
|
||||
// checkLocal checks s for validity, independently of other schemas it may refer to.
|
||||
// Since checking a regexp involves compiling it, checkLocal saves those compiled regexps
|
||||
// in the schema for later use.
|
||||
// It appends the errors it finds to errs.
|
||||
func (s *Schema) checkLocal(report func(error), infos map[*Schema]*resolvedInfo) {
|
||||
addf := func(format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
report(fmt.Errorf("jsonschema.Schema: %s: %s", s, msg))
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
addf("nil subschema")
|
||||
return
|
||||
}
|
||||
if err := s.basicChecks(); err != nil {
|
||||
report(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: validate the schema's properties,
|
||||
// ideally by jsonschema-validating it against the meta-schema.
|
||||
|
||||
// Some properties are present so that Schemas can round-trip, but we do not
|
||||
// validate them.
|
||||
// Currently, it's just the $vocabulary property.
|
||||
// As a special case, we can validate the 2020-12 meta-schema.
|
||||
if s.Vocabulary != nil && s.Schema != draft202012SchemaVersion {
|
||||
addf("cannot validate a schema with $vocabulary")
|
||||
}
|
||||
|
||||
info := infos[s]
|
||||
|
||||
// Check and compile regexps.
|
||||
if s.Pattern != "" {
|
||||
re, err := regexp.Compile(s.Pattern)
|
||||
if err != nil {
|
||||
addf("pattern: %v", err)
|
||||
} else {
|
||||
info.pattern = re
|
||||
}
|
||||
}
|
||||
if len(s.PatternProperties) > 0 {
|
||||
info.patternProperties = map[*regexp.Regexp]*Schema{}
|
||||
for reString, subschema := range s.PatternProperties {
|
||||
re, err := regexp.Compile(reString)
|
||||
if err != nil {
|
||||
addf("patternProperties[%q]: %v", reString, err)
|
||||
continue
|
||||
}
|
||||
info.patternProperties[re] = subschema
|
||||
}
|
||||
}
|
||||
|
||||
// Build a set of required properties, to avoid quadratic behavior when validating
|
||||
// a struct.
|
||||
if len(s.Required) > 0 {
|
||||
info.isRequired = map[string]bool{}
|
||||
for _, r := range s.Required {
|
||||
info.isRequired[r] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolveURIs resolves the ids and anchors in all the schemas of root, relative
|
||||
// to baseURI.
|
||||
// See https://json-schema.org/draft/2020-12/json-schema-core#section-8.2, section
|
||||
// 8.2.1.
|
||||
//
|
||||
// Every schema has a base URI and a parent base URI.
|
||||
//
|
||||
// The parent base URI is the base URI of the lexically enclosing schema, or for
|
||||
// a root schema, the URI it was loaded from or the one supplied to [Schema.Resolve].
|
||||
//
|
||||
// If the schema has no $id property, the base URI of a schema is that of its parent.
|
||||
// If the schema does have an $id, it must be a URI, possibly relative. The schema's
|
||||
// base URI is the $id resolved (in the sense of [url.URL.ResolveReference]) against
|
||||
// the parent base.
|
||||
//
|
||||
// As an example, consider this schema loaded from http://a.com/root.json (quotes omitted):
|
||||
//
|
||||
// {
|
||||
// allOf: [
|
||||
// {$id: "sub1.json", minLength: 5},
|
||||
// {$id: "http://b.com", minimum: 10},
|
||||
// {not: {maximum: 20}}
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// The base URIs are as follows. Schema locations are expressed in the JSON Pointer notation.
|
||||
//
|
||||
// schema base URI
|
||||
// root http://a.com/root.json
|
||||
// allOf/0 http://a.com/sub1.json
|
||||
// allOf/1 http://b.com (absolute $id; doesn't matter that it's not under the loaded URI)
|
||||
// allOf/2 http://a.com/root.json (inherited from parent)
|
||||
// allOf/2/not http://a.com/root.json (inherited from parent)
|
||||
func resolveURIs(rs *Resolved, baseURI *url.URL) error {
|
||||
// Anchors and dynamic anchors are URI fragments that are scoped to their base.
|
||||
// We treat them as keys in a map stored within the schema.
|
||||
setAnchor := func(s *Schema, baseInfo *resolvedInfo, anchor string, dynamic bool) error {
|
||||
if anchor != "" {
|
||||
if _, ok := baseInfo.anchors[anchor]; ok {
|
||||
return fmt.Errorf("duplicate anchor %q in %s", anchor, baseInfo.uri)
|
||||
}
|
||||
if baseInfo.anchors == nil {
|
||||
baseInfo.anchors = map[string]anchorInfo{}
|
||||
}
|
||||
baseInfo.anchors[anchor] = anchorInfo{s, dynamic}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var resolve func(s, base *Schema) error
|
||||
resolve = func(s, base *Schema) error {
|
||||
info := rs.resolvedInfos[s]
|
||||
baseInfo := rs.resolvedInfos[base]
|
||||
|
||||
// ids are scoped to the root.
|
||||
if s.ID != "" {
|
||||
// draft-7 specific
|
||||
// https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.8.3
|
||||
// "All other properties in a "$ref" object MUST be ignored."
|
||||
ignore := rs.draft == draft7 && s.Ref != ""
|
||||
if !ignore {
|
||||
// A non-empty ID establishes a new base.
|
||||
idURI, err := url.Parse(s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.draft == draft2020 && idURI.Fragment != "" {
|
||||
return fmt.Errorf("$id %s must not have a fragment", s.ID)
|
||||
}
|
||||
if rs.draft == draft7 && idURI.Fragment != "" {
|
||||
// anchor did not exist in draft 7, id was used for base uri and document navigation
|
||||
// https://json-schema.org/draft-07/draft-handrews-json-schema-01#id-keyword
|
||||
anchorName := strings.TrimPrefix(s.ID, "#")
|
||||
setAnchor(s, baseInfo, anchorName, false)
|
||||
} else {
|
||||
// The base URI for this schema is its $id resolved against the parent base.
|
||||
info.uri = baseInfo.uri.ResolveReference(idURI)
|
||||
if !info.uri.IsAbs() {
|
||||
return fmt.Errorf("$id %s does not resolve to an absolute URI (base is %q)", s.ID, baseInfo.uri)
|
||||
}
|
||||
rs.resolvedURIs[info.uri.String()] = s
|
||||
base = s // needed for anchors
|
||||
baseInfo = rs.resolvedInfos[base]
|
||||
}
|
||||
}
|
||||
}
|
||||
info.base = base
|
||||
if rs.draft == draft2020 {
|
||||
setAnchor(s, baseInfo, s.Anchor, false)
|
||||
setAnchor(s, baseInfo, s.DynamicAnchor, true)
|
||||
}
|
||||
|
||||
for c := range s.children() {
|
||||
if err := resolve(c, base); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the root URI to the base for now. If the root has an $id, this will change.
|
||||
rs.resolvedInfos[rs.root].uri = baseURI
|
||||
// The original base, even if changed, is still a valid way to refer to the root.
|
||||
rs.resolvedURIs[baseURI.String()] = rs.root
|
||||
|
||||
return resolve(rs.root, rs.root)
|
||||
}
|
||||
|
||||
// resolveRefs replaces every ref in the schemas with the schema it refers to.
|
||||
// A reference that doesn't resolve within the schema may refer to some other schema
|
||||
// that needs to be loaded.
|
||||
func (r *resolver) resolveRefs(rs *Resolved) error {
|
||||
for s := range rs.root.all() {
|
||||
info := rs.resolvedInfos[s]
|
||||
if s.Ref != "" {
|
||||
refSchema, _, err := r.resolveRef(rs, s, s.Ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Whether or not the anchor referred to by $ref fragment is dynamic,
|
||||
// the ref still treats it lexically.
|
||||
info.resolvedRef = refSchema
|
||||
}
|
||||
if s.DynamicRef != "" {
|
||||
refSchema, frag, err := r.resolveRef(rs, s, s.DynamicRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frag != "" {
|
||||
// The dynamic ref's fragment points to a dynamic anchor.
|
||||
// We must resolve the fragment at validation time.
|
||||
info.dynamicRefAnchor = frag
|
||||
} else {
|
||||
// There is no dynamic anchor in the lexically referenced schema,
|
||||
// so the dynamic ref behaves like a lexical ref.
|
||||
info.resolvedDynamicRef = refSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveRef resolves the reference ref, which is either s.Ref or s.DynamicRef.
|
||||
func (r *resolver) resolveRef(rs *Resolved, s *Schema, ref string) (_ *Schema, dynamicFragment string, err error) {
|
||||
refURI, err := url.Parse(ref)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// URI-resolve the ref against the current base URI to get a complete URI.
|
||||
base := rs.resolvedInfos[s].base
|
||||
refURI = rs.resolvedInfos[base].uri.ResolveReference(refURI)
|
||||
// The non-fragment part of a ref URI refers to the base URI of some schema.
|
||||
// This part is the same for dynamic refs too: their non-fragment part resolves
|
||||
// lexically.
|
||||
u := *refURI
|
||||
u.Fragment = ""
|
||||
fraglessRefURI := &u
|
||||
// Look it up locally.
|
||||
referencedSchema := rs.resolvedURIs[fraglessRefURI.String()]
|
||||
if referencedSchema == nil {
|
||||
// The schema is remote. Maybe we've already loaded it.
|
||||
// We assume that the non-fragment part of refURI refers to a top-level schema
|
||||
// document. That is, we don't support the case exemplified by
|
||||
// http://foo.com/bar.json/baz, where the document is in bar.json and
|
||||
// the reference points to a subschema within it.
|
||||
// TODO: support that case.
|
||||
if lrs := r.loaded[fraglessRefURI.String()]; lrs != nil {
|
||||
referencedSchema = lrs.root
|
||||
} else {
|
||||
// Try to load the schema.
|
||||
ls, err := r.opts.Loader(fraglessRefURI)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("loading %s: %w", fraglessRefURI, err)
|
||||
}
|
||||
// Check if referenced schema has $schema defined. If not it should inherit the resolved
|
||||
if ls.Schema == "" {
|
||||
ls.Schema = s.Schema
|
||||
}
|
||||
lrs, err := r.resolve(ls, fraglessRefURI)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
referencedSchema = lrs.root
|
||||
assert(referencedSchema != nil, "nil referenced schema")
|
||||
// Copy the resolvedInfos from lrs into rs, without overwriting
|
||||
// (hence we can't use maps.Insert).
|
||||
for s, i := range lrs.resolvedInfos {
|
||||
if rs.resolvedInfos[s] == nil {
|
||||
rs.resolvedInfos[s] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frag := refURI.Fragment
|
||||
// Look up frag in refSchema.
|
||||
// frag is either a JSON Pointer or the name of an anchor.
|
||||
// A JSON Pointer is either the empty string or begins with a '/',
|
||||
// whereas anchors are always non-empty strings that don't contain slashes.
|
||||
if frag != "" && !strings.HasPrefix(frag, "/") {
|
||||
resInfo := rs.resolvedInfos[referencedSchema]
|
||||
info, found := resInfo.anchors[frag]
|
||||
|
||||
if !found {
|
||||
return nil, "", fmt.Errorf("no anchor %q in %s", frag, s)
|
||||
}
|
||||
if info.dynamic {
|
||||
dynamicFragment = frag
|
||||
}
|
||||
return info.schema, dynamicFragment, nil
|
||||
}
|
||||
// frag is a JSON Pointer.
|
||||
s, err = dereferenceJSONPointer(referencedSchema, frag)
|
||||
return s, "", err
|
||||
}
|
||||
642
vendor/github.com/google/jsonschema-go/jsonschema/schema.go
generated
vendored
Normal file
642
vendor/github.com/google/jsonschema-go/jsonschema/schema.go
generated
vendored
Normal file
@@ -0,0 +1,642 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// A Schema is a JSON schema object.
|
||||
// It supports both draft-07 and the 2020-12 draft specifications:
|
||||
// - Draft-07: https://json-schema.org/draft-07/draft-handrews-json-schema-01
|
||||
// and https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01
|
||||
// - Draft 2020-12: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01
|
||||
// and https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01
|
||||
//
|
||||
// A Schema value may have non-zero values for more than one field:
|
||||
// all relevant non-zero fields are used for validation.
|
||||
// There is one exception to provide more Go type-safety: the Type and Types fields
|
||||
// are mutually exclusive.
|
||||
//
|
||||
// Since this struct is a Go representation of a JSON value, it inherits JSON's
|
||||
// distinction between nil and empty. Nil slices and maps are considered absent,
|
||||
// but empty ones are present and affect validation. For example,
|
||||
//
|
||||
// Schema{Enum: nil}
|
||||
//
|
||||
// is equivalent to an empty schema, so it validates every instance. But
|
||||
//
|
||||
// Schema{Enum: []any{}}
|
||||
//
|
||||
// requires equality to some slice element, so it vacuously rejects every instance.
|
||||
type Schema struct {
|
||||
// core
|
||||
ID string `json:"$id,omitempty"`
|
||||
Schema string `json:"$schema,omitempty"`
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Comment string `json:"$comment,omitempty"`
|
||||
Defs map[string]*Schema `json:"$defs,omitempty"`
|
||||
Definitions map[string]*Schema `json:"definitions,omitempty"`
|
||||
|
||||
// split draft 7 Dependencies into DependencySchemas and DependencyStrings
|
||||
DependencySchemas map[string]*Schema `json:"-"`
|
||||
DependencyStrings map[string][]string `json:"-"`
|
||||
|
||||
Anchor string `json:"$anchor,omitempty"`
|
||||
DynamicAnchor string `json:"$dynamicAnchor,omitempty"`
|
||||
DynamicRef string `json:"$dynamicRef,omitempty"`
|
||||
Vocabulary map[string]bool `json:"$vocabulary,omitempty"`
|
||||
|
||||
// metadata
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Default json.RawMessage `json:"default,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
WriteOnly bool `json:"writeOnly,omitempty"`
|
||||
Examples []any `json:"examples,omitempty"`
|
||||
|
||||
// validation
|
||||
// Use Type for a single type, or Types for multiple types; never both.
|
||||
Type string `json:"-"`
|
||||
Types []string `json:"-"`
|
||||
Enum []any `json:"enum,omitempty"`
|
||||
// Const is *any because a JSON null (Go nil) is a valid value.
|
||||
Const *any `json:"const,omitempty"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
||||
Minimum *float64 `json:"minimum,omitempty"`
|
||||
Maximum *float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
|
||||
ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
|
||||
MinLength *int `json:"minLength,omitempty"`
|
||||
MaxLength *int `json:"maxLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
|
||||
// arrays
|
||||
PrefixItems []*Schema `json:"prefixItems,omitempty"`
|
||||
Items *Schema `json:"-"`
|
||||
ItemsArray []*Schema `json:"-"`
|
||||
MinItems *int `json:"minItems,omitempty"`
|
||||
MaxItems *int `json:"maxItems,omitempty"`
|
||||
AdditionalItems *Schema `json:"additionalItems,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
Contains *Schema `json:"contains,omitempty"`
|
||||
MinContains *int `json:"minContains,omitempty"` // *int, not int: default is 1, not 0
|
||||
MaxContains *int `json:"maxContains,omitempty"`
|
||||
UnevaluatedItems *Schema `json:"unevaluatedItems,omitempty"`
|
||||
|
||||
// objects
|
||||
MinProperties *int `json:"minProperties,omitempty"`
|
||||
MaxProperties *int `json:"maxProperties,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
DependentRequired map[string][]string `json:"dependentRequired,omitempty"`
|
||||
Properties map[string]*Schema `json:"properties,omitempty"`
|
||||
PatternProperties map[string]*Schema `json:"patternProperties,omitempty"`
|
||||
AdditionalProperties *Schema `json:"additionalProperties,omitempty"`
|
||||
PropertyNames *Schema `json:"propertyNames,omitempty"`
|
||||
UnevaluatedProperties *Schema `json:"unevaluatedProperties,omitempty"`
|
||||
|
||||
// logic
|
||||
AllOf []*Schema `json:"allOf,omitempty"`
|
||||
AnyOf []*Schema `json:"anyOf,omitempty"`
|
||||
OneOf []*Schema `json:"oneOf,omitempty"`
|
||||
Not *Schema `json:"not,omitempty"`
|
||||
|
||||
// conditional
|
||||
If *Schema `json:"if,omitempty"`
|
||||
Then *Schema `json:"then,omitempty"`
|
||||
Else *Schema `json:"else,omitempty"`
|
||||
DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"`
|
||||
|
||||
// other
|
||||
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8
|
||||
ContentEncoding string `json:"contentEncoding,omitempty"`
|
||||
ContentMediaType string `json:"contentMediaType,omitempty"`
|
||||
ContentSchema *Schema `json:"contentSchema,omitempty"`
|
||||
|
||||
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
|
||||
Format string `json:"format,omitempty"`
|
||||
|
||||
// Extra allows for additional keywords beyond those specified.
|
||||
Extra map[string]any `json:"-"`
|
||||
|
||||
// PropertyOrder records the ordering of properties for JSON rendering.
|
||||
//
|
||||
// During [For], PropertyOrder is set to the field order,
|
||||
// if the type used for inference is a struct.
|
||||
//
|
||||
// If PropertyOrder is set, it controls the relative ordering of properties in [Schema.MarshalJSON].
|
||||
// The rendered JSON first lists any properties that appear in the PropertyOrder slice in the order
|
||||
// they appear, followed by all other properties that do not appear in the PropertyOrder slice in an
|
||||
// undefined but deterministic order.
|
||||
PropertyOrder []string `json:"-"`
|
||||
}
|
||||
|
||||
// falseSchema returns a new Schema tree that fails to validate any value.
|
||||
func falseSchema() *Schema {
|
||||
return &Schema{Not: &Schema{}}
|
||||
}
|
||||
|
||||
// anchorInfo records the subschema to which an anchor refers, and whether
|
||||
// the anchor keyword is $anchor or $dynamicAnchor.
|
||||
type anchorInfo struct {
|
||||
schema *Schema
|
||||
dynamic bool
|
||||
}
|
||||
|
||||
// String returns a short description of the schema.
|
||||
func (s *Schema) String() string {
|
||||
if s.ID != "" {
|
||||
return s.ID
|
||||
}
|
||||
if a := cmp.Or(s.Anchor, s.DynamicAnchor); a != "" {
|
||||
return fmt.Sprintf("anchor %s", a)
|
||||
}
|
||||
return "<anonymous schema>"
|
||||
}
|
||||
|
||||
// CloneSchemas returns a copy of s.
|
||||
// The copy is shallow except for sub-schemas, which are themelves copied with CloneSchemas.
|
||||
// This allows both s and s.CloneSchemas() to appear as sub-schemas of the same parent.
|
||||
func (s *Schema) CloneSchemas() *Schema {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
s2 := *s
|
||||
v := reflect.ValueOf(&s2)
|
||||
for _, info := range schemaFieldInfos {
|
||||
fv := v.Elem().FieldByIndex(info.sf.Index)
|
||||
switch info.sf.Type {
|
||||
case schemaType:
|
||||
sscss := fv.Interface().(*Schema)
|
||||
fv.Set(reflect.ValueOf(sscss.CloneSchemas()))
|
||||
|
||||
case schemaSliceType:
|
||||
slice := fv.Interface().([]*Schema)
|
||||
slice = slices.Clone(slice)
|
||||
for i, ss := range slice {
|
||||
slice[i] = ss.CloneSchemas()
|
||||
}
|
||||
fv.Set(reflect.ValueOf(slice))
|
||||
|
||||
case schemaMapType:
|
||||
m := fv.Interface().(map[string]*Schema)
|
||||
m = maps.Clone(m)
|
||||
for k, ss := range m {
|
||||
m[k] = ss.CloneSchemas()
|
||||
}
|
||||
fv.Set(reflect.ValueOf(m))
|
||||
|
||||
}
|
||||
}
|
||||
return &s2
|
||||
}
|
||||
|
||||
func (s *Schema) basicChecks() error {
|
||||
if s.Type != "" && s.Types != nil {
|
||||
return errors.New("both Type and Types are set; at most one should be")
|
||||
}
|
||||
if s.Defs != nil && s.Definitions != nil {
|
||||
return errors.New("both Defs and Definitions are set; at most one should be")
|
||||
}
|
||||
if s.Items != nil && s.ItemsArray != nil {
|
||||
return errors.New("both Items and ItemsArray are set; at most one should be")
|
||||
}
|
||||
propertyOrderSeen := make(map[string]bool)
|
||||
for _, val := range s.PropertyOrder {
|
||||
if _, ok := propertyOrderSeen[val]; ok {
|
||||
// Duplicate found
|
||||
return fmt.Errorf("property order slice cannot contain duplicate entries, found duplicate %q", val)
|
||||
}
|
||||
propertyOrderSeen[val] = true
|
||||
}
|
||||
|
||||
for key := range s.DependencySchemas {
|
||||
// Check if the key exists in the dependency strings map
|
||||
if _, exists := s.DependencyStrings[key]; exists {
|
||||
return fmt.Errorf("dependency key %q cannot be defined as both a schema and a string array", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type schemaWithoutMethods Schema // doesn't implement json.{Unm,M}arshaler
|
||||
|
||||
func (s Schema) MarshalJSON() ([]byte, error) {
|
||||
// NOTE: Use a value receiver here to avoid the encoding/json bugs
|
||||
// described in golang/go#22967, golang/go#33993, and golang/go#55890.
|
||||
// With a pointer receiver, MarshalJSON is only called for Schema in
|
||||
// some cases (for example when the field value is addressable, or not
|
||||
// stored as a map value), which leads to inconsistent JSON encoding.
|
||||
// A value receiver makes Schema itself implement json.Marshaler and
|
||||
// ensures that encoding/json always calls this method.
|
||||
if err := s.basicChecks(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Marshal either Type or Types as "type".
|
||||
var typ any
|
||||
switch {
|
||||
case s.Type != "":
|
||||
typ = s.Type
|
||||
case s.Types != nil:
|
||||
typ = s.Types
|
||||
}
|
||||
|
||||
var items any
|
||||
switch {
|
||||
case s.Items != nil:
|
||||
items = s.Items
|
||||
case s.ItemsArray != nil:
|
||||
items = s.ItemsArray
|
||||
}
|
||||
|
||||
var dep map[string]any
|
||||
size := len(s.DependencySchemas) + len(s.DependencyStrings)
|
||||
if size > 0 {
|
||||
dep = make(map[string]any, size)
|
||||
for k, v := range s.DependencySchemas {
|
||||
dep[k] = v
|
||||
}
|
||||
for k, v := range s.DependencyStrings {
|
||||
dep[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
ms := struct {
|
||||
Type any `json:"type,omitempty"`
|
||||
Properties json.Marshaler `json:"properties,omitempty"`
|
||||
Dependencies map[string]any `json:"dependencies,omitempty"`
|
||||
Items any `json:"items,omitempty"`
|
||||
*schemaWithoutMethods
|
||||
}{
|
||||
Type: typ,
|
||||
Dependencies: dep,
|
||||
Items: items,
|
||||
schemaWithoutMethods: (*schemaWithoutMethods)(&s),
|
||||
}
|
||||
// Marshal properties, even if the empty map (but not nil).
|
||||
if s.Properties != nil {
|
||||
ms.Properties = orderedProperties{
|
||||
props: s.Properties,
|
||||
order: s.PropertyOrder,
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := marshalStructWithMap(&ms, "Extra")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Marshal {} as true and {"not": {}} as false.
|
||||
// It is wasteful to do this here instead of earlier, but much easier.
|
||||
switch {
|
||||
case bytes.Equal(bs, []byte(`{}`)):
|
||||
bs = []byte("true")
|
||||
case bytes.Equal(bs, []byte(`{"not":true}`)):
|
||||
bs = []byte("false")
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// orderedProperties is a helper to marshal the properties map in a specific order.
|
||||
type orderedProperties struct {
|
||||
props map[string]*Schema
|
||||
order []string
|
||||
}
|
||||
|
||||
func (op orderedProperties) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
|
||||
first := true
|
||||
processed := make(map[string]bool, len(op.props))
|
||||
|
||||
// Helper closure to write "key": value
|
||||
writeEntry := func(key string, val *Schema) error {
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
first = false
|
||||
|
||||
// Marshal the Key
|
||||
keyBytes, err := json.Marshal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Write(keyBytes)
|
||||
|
||||
buf.WriteByte(':')
|
||||
|
||||
// Marshal the Value
|
||||
valBytes, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Write(valBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write keys explicitly listed in PropertyOrder
|
||||
for _, name := range op.order {
|
||||
if prop, ok := op.props[name]; ok {
|
||||
if err := writeEntry(name, prop); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processed[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Write any remaining keys
|
||||
var remaining []string
|
||||
for name := range op.props {
|
||||
if !processed[name] {
|
||||
remaining = append(remaining, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the slice alphabetically
|
||||
slices.Sort(remaining)
|
||||
|
||||
for _, name := range remaining {
|
||||
if err := writeEntry(name, op.props[name]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *Schema) UnmarshalJSON(data []byte) error {
|
||||
// A JSON boolean is a valid schema.
|
||||
var b bool
|
||||
if err := json.Unmarshal(data, &b); err == nil {
|
||||
if b {
|
||||
// true is the empty schema, which validates everything.
|
||||
*s = Schema{}
|
||||
} else {
|
||||
// false is the schema that validates nothing.
|
||||
*s = *falseSchema()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ms := struct {
|
||||
Type json.RawMessage `json:"type,omitempty"`
|
||||
Dependencies map[string]json.RawMessage `json:"dependencies,omitempty"`
|
||||
Items json.RawMessage `json:"items,omitempty"`
|
||||
Const json.RawMessage `json:"const,omitempty"`
|
||||
MinLength *integer `json:"minLength,omitempty"`
|
||||
MaxLength *integer `json:"maxLength,omitempty"`
|
||||
MinItems *integer `json:"minItems,omitempty"`
|
||||
MaxItems *integer `json:"maxItems,omitempty"`
|
||||
MinProperties *integer `json:"minProperties,omitempty"`
|
||||
MaxProperties *integer `json:"maxProperties,omitempty"`
|
||||
MinContains *integer `json:"minContains,omitempty"`
|
||||
MaxContains *integer `json:"maxContains,omitempty"`
|
||||
|
||||
*schemaWithoutMethods
|
||||
}{
|
||||
schemaWithoutMethods: (*schemaWithoutMethods)(s),
|
||||
}
|
||||
if err := unmarshalStructWithMap(data, &ms, "Extra"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Unmarshal "type" as either Type or Types.
|
||||
var err error
|
||||
if len(ms.Type) > 0 {
|
||||
switch ms.Type[0] {
|
||||
case '"':
|
||||
err = json.Unmarshal(ms.Type, &s.Type)
|
||||
case '[':
|
||||
err = json.Unmarshal(ms.Type, &s.Types)
|
||||
default:
|
||||
err = fmt.Errorf(`invalid value for "type": %q`, ms.Type)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal "items" as either Items or ItemsArray.
|
||||
if len(ms.Items) > 0 {
|
||||
switch ms.Items[0] {
|
||||
case '[':
|
||||
var schemas []*Schema
|
||||
err = json.Unmarshal(ms.Items, &schemas)
|
||||
s.ItemsArray = schemas
|
||||
default:
|
||||
var schema Schema
|
||||
err = json.Unmarshal(ms.Items, &schema)
|
||||
s.Items = &schema
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal "Dependencies" values as either string arrays or schemas
|
||||
// and assign them to specific map DependencySchemas or DependencyStrings.
|
||||
for k, v := range ms.Dependencies {
|
||||
if len(v) > 0 {
|
||||
switch v[0] {
|
||||
case '[':
|
||||
var dstrings []string
|
||||
err = json.Unmarshal(v, &dstrings)
|
||||
if s.DependencyStrings == nil {
|
||||
s.DependencyStrings = make(map[string][]string)
|
||||
}
|
||||
s.DependencyStrings[k] = dstrings
|
||||
default:
|
||||
var dschema Schema
|
||||
err = json.Unmarshal(v, &dschema)
|
||||
if s.DependencySchemas == nil {
|
||||
s.DependencySchemas = make(map[string]*Schema)
|
||||
}
|
||||
s.DependencySchemas[k] = &dschema
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
unmarshalAnyPtr := func(p **any, raw json.RawMessage) error {
|
||||
if len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(raw, []byte("null")) {
|
||||
*p = new(any)
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(raw, p)
|
||||
}
|
||||
|
||||
// Setting Const to a pointer to null will marshal properly, but won't
|
||||
// unmarshal: the *any is set to nil, not a pointer to nil.
|
||||
if err := unmarshalAnyPtr(&s.Const, ms.Const); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set := func(dst **int, src *integer) {
|
||||
if src != nil {
|
||||
*dst = Ptr(int(*src))
|
||||
}
|
||||
}
|
||||
|
||||
set(&s.MinLength, ms.MinLength)
|
||||
set(&s.MaxLength, ms.MaxLength)
|
||||
set(&s.MinItems, ms.MinItems)
|
||||
set(&s.MaxItems, ms.MaxItems)
|
||||
set(&s.MinProperties, ms.MinProperties)
|
||||
set(&s.MaxProperties, ms.MaxProperties)
|
||||
set(&s.MinContains, ms.MinContains)
|
||||
set(&s.MaxContains, ms.MaxContains)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type integer int32 // for the integer-valued fields of Schema
|
||||
|
||||
func (ip *integer) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
// If there is a decimal point, src is a floating-point number.
|
||||
var i int64
|
||||
if bytes.ContainsRune(data, '.') {
|
||||
var f float64
|
||||
if err := json.Unmarshal(data, &f); err != nil {
|
||||
return errors.New("not a number")
|
||||
}
|
||||
i = int64(f)
|
||||
if float64(i) != f {
|
||||
return errors.New("not an integer value")
|
||||
}
|
||||
} else {
|
||||
if err := json.Unmarshal(data, &i); err != nil {
|
||||
return errors.New("cannot be unmarshaled into an int")
|
||||
}
|
||||
}
|
||||
// Ensure behavior is the same on both 32-bit and 64-bit systems.
|
||||
if i < math.MinInt32 || i > math.MaxInt32 {
|
||||
return errors.New("integer is out of range")
|
||||
}
|
||||
*ip = integer(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ptr returns a pointer to a new variable whose value is x.
|
||||
func Ptr[T any](x T) *T { return &x }
|
||||
|
||||
// every applies f preorder to every schema under s including s.
|
||||
// The second argument to f is the path to the schema appended to the argument path.
|
||||
// It stops when f returns false.
|
||||
func (s *Schema) every(f func(*Schema) bool) bool {
|
||||
return f(s) && s.everyChild(func(s *Schema) bool { return s.every(f) })
|
||||
}
|
||||
|
||||
// everyChild reports whether f is true for every immediate child schema of s.
|
||||
func (s *Schema) everyChild(f func(*Schema) bool) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
for _, info := range schemaFieldInfos {
|
||||
fv := v.Elem().FieldByIndex(info.sf.Index)
|
||||
switch info.sf.Type {
|
||||
case schemaType:
|
||||
// A field that contains an individual schema. A nil is valid: it just means the field isn't present.
|
||||
c := fv.Interface().(*Schema)
|
||||
if c != nil && !f(c) {
|
||||
return false
|
||||
}
|
||||
|
||||
case schemaSliceType:
|
||||
slice := fv.Interface().([]*Schema)
|
||||
for _, c := range slice {
|
||||
if !f(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
case schemaMapType:
|
||||
// Sort keys for determinism.
|
||||
m := fv.Interface().(map[string]*Schema)
|
||||
for _, k := range slices.Sorted(maps.Keys(m)) {
|
||||
if !f(m[k]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// all wraps every in an iterator.
|
||||
func (s *Schema) all() iter.Seq[*Schema] {
|
||||
return func(yield func(*Schema) bool) { s.every(yield) }
|
||||
}
|
||||
|
||||
// children wraps everyChild in an iterator.
|
||||
func (s *Schema) children() iter.Seq[*Schema] {
|
||||
return func(yield func(*Schema) bool) { s.everyChild(yield) }
|
||||
}
|
||||
|
||||
var (
|
||||
schemaType = reflect.TypeFor[*Schema]()
|
||||
schemaSliceType = reflect.TypeFor[[]*Schema]()
|
||||
schemaMapType = reflect.TypeFor[map[string]*Schema]()
|
||||
)
|
||||
|
||||
type structFieldInfo struct {
|
||||
sf reflect.StructField
|
||||
jsonName string
|
||||
}
|
||||
|
||||
var (
|
||||
// the visible fields of Schema that have a JSON name, sorted by that name
|
||||
schemaFieldInfos []structFieldInfo
|
||||
// map from JSON name to field
|
||||
schemaFieldMap = map[string]reflect.StructField{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
t := reflect.VisibleFields(reflect.TypeFor[Schema]())
|
||||
for _, sf := range t {
|
||||
info := fieldJSONInfo(sf)
|
||||
if !info.omit {
|
||||
schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{sf, info.name})
|
||||
} else {
|
||||
// jsoninfo.name is used to build the info paths. The items and dependencies are ommited,
|
||||
// since the original fields are separated to handle the union types supported in json and
|
||||
// these fields have custom marshalling and unmarshalling logic.
|
||||
// we still need these fields in schemaFieldInfos for creating schema trees and calculating paths and refs.
|
||||
// so we manually create them and assign the jsonName to the original field json name.
|
||||
switch sf.Name {
|
||||
case "Items", "ItemsArray":
|
||||
schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{sf, "items"})
|
||||
case "DependencySchemas", "DependencyStrings":
|
||||
schemaFieldInfos = append(schemaFieldInfos, structFieldInfo{sf, "dependencies"})
|
||||
}
|
||||
}
|
||||
}
|
||||
// The value of "dependencies" this sort of schemaFieldInfos.
|
||||
// This sort is unstable and is comparing the json.names of DependencyStrings and DependencySchemas which are both "dependencies".
|
||||
// Since the sort is unstable it cannot be guarantied that "dependencies" has the DependencySchemas value.
|
||||
slices.SortFunc(schemaFieldInfos, func(i1, i2 structFieldInfo) int {
|
||||
return cmp.Compare(i1.jsonName, i2.jsonName)
|
||||
})
|
||||
for _, info := range schemaFieldInfos {
|
||||
schemaFieldMap[info.jsonName] = info.sf
|
||||
}
|
||||
}
|
||||
463
vendor/github.com/google/jsonschema-go/jsonschema/util.go
generated
vendored
Normal file
463
vendor/github.com/google/jsonschema-go/jsonschema/util.go
generated
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/maphash"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Equal reports whether two Go values representing JSON values are equal according
|
||||
// to the JSON Schema spec.
|
||||
// The values must not contain cycles.
|
||||
// See https://json-schema.org/draft/2020-12/json-schema-core#section-4.2.2.
|
||||
// It behaves like reflect.DeepEqual, except that numbers are compared according
|
||||
// to mathematical equality.
|
||||
func Equal(x, y any) bool {
|
||||
return equalValue(reflect.ValueOf(x), reflect.ValueOf(y))
|
||||
}
|
||||
|
||||
func equalValue(x, y reflect.Value) bool {
|
||||
// Copied from src/reflect/deepequal.go, omitting the visited check (because JSON
|
||||
// values are trees).
|
||||
if !x.IsValid() || !y.IsValid() {
|
||||
return x.IsValid() == y.IsValid()
|
||||
}
|
||||
|
||||
// Treat numbers specially.
|
||||
rx, ok1 := jsonNumber(x)
|
||||
ry, ok2 := jsonNumber(y)
|
||||
if ok1 && ok2 {
|
||||
return rx.Cmp(ry) == 0
|
||||
}
|
||||
if x.Kind() != y.Kind() {
|
||||
return false
|
||||
}
|
||||
switch x.Kind() {
|
||||
case reflect.Array:
|
||||
if x.Len() != y.Len() {
|
||||
return false
|
||||
}
|
||||
for i := range x.Len() {
|
||||
if !equalValue(x.Index(i), y.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Slice:
|
||||
if x.IsNil() != y.IsNil() {
|
||||
return false
|
||||
}
|
||||
if x.Len() != y.Len() {
|
||||
return false
|
||||
}
|
||||
if x.UnsafePointer() == y.UnsafePointer() {
|
||||
return true
|
||||
}
|
||||
// Special case for []byte, which is common.
|
||||
if x.Type().Elem().Kind() == reflect.Uint8 && x.Type() == y.Type() {
|
||||
return bytes.Equal(x.Bytes(), y.Bytes())
|
||||
}
|
||||
for i := range x.Len() {
|
||||
if !equalValue(x.Index(i), y.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Interface:
|
||||
if x.IsNil() || y.IsNil() {
|
||||
return x.IsNil() == y.IsNil()
|
||||
}
|
||||
return equalValue(x.Elem(), y.Elem())
|
||||
case reflect.Pointer:
|
||||
if x.UnsafePointer() == y.UnsafePointer() {
|
||||
return true
|
||||
}
|
||||
return equalValue(x.Elem(), y.Elem())
|
||||
case reflect.Struct:
|
||||
t := x.Type()
|
||||
if t != y.Type() {
|
||||
return false
|
||||
}
|
||||
for i := range t.NumField() {
|
||||
sf := t.Field(i)
|
||||
if !sf.IsExported() {
|
||||
continue
|
||||
}
|
||||
if !equalValue(x.FieldByIndex(sf.Index), y.FieldByIndex(sf.Index)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Map:
|
||||
if x.IsNil() != y.IsNil() {
|
||||
return false
|
||||
}
|
||||
if x.Len() != y.Len() {
|
||||
return false
|
||||
}
|
||||
if x.UnsafePointer() == y.UnsafePointer() {
|
||||
return true
|
||||
}
|
||||
iter := x.MapRange()
|
||||
for iter.Next() {
|
||||
vx := iter.Value()
|
||||
vy := y.MapIndex(iter.Key())
|
||||
if !vy.IsValid() || !equalValue(vx, vy) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Func:
|
||||
if x.Type() != y.Type() {
|
||||
return false
|
||||
}
|
||||
if x.IsNil() && y.IsNil() {
|
||||
return true
|
||||
}
|
||||
panic("cannot compare functions")
|
||||
case reflect.String:
|
||||
return x.String() == y.String()
|
||||
case reflect.Bool:
|
||||
return x.Bool() == y.Bool()
|
||||
// Ints, uints and floats handled in jsonNumber, at top of function.
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported kind: %s", x.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// hashValue adds v to the data hashed by h. v must not have cycles.
|
||||
// hashValue panics if the value contains functions or channels, or maps whose
|
||||
// key type is not string.
|
||||
// It ignores unexported fields of structs.
|
||||
// Calls to hashValue with the equal values (in the sense
|
||||
// of [Equal]) result in the same sequence of values written to the hash.
|
||||
func hashValue(h *maphash.Hash, v reflect.Value) {
|
||||
// TODO: replace writes of basic types with WriteComparable in 1.24.
|
||||
|
||||
writeUint := func(u uint64) {
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], u)
|
||||
h.Write(buf[:])
|
||||
}
|
||||
|
||||
var write func(reflect.Value)
|
||||
write = func(v reflect.Value) {
|
||||
if r, ok := jsonNumber(v); ok {
|
||||
// We want 1.0 and 1 to hash the same.
|
||||
// big.Rats are always normalized, so they will be.
|
||||
// We could do this more efficiently by handling the int and float cases
|
||||
// separately, but that's premature.
|
||||
writeUint(uint64(r.Sign() + 1))
|
||||
h.Write(r.Num().Bytes())
|
||||
h.Write(r.Denom().Bytes())
|
||||
return
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
h.WriteByte(0)
|
||||
case reflect.String:
|
||||
h.WriteString(v.String())
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
h.WriteByte(1)
|
||||
} else {
|
||||
h.WriteByte(0)
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
c := v.Complex()
|
||||
writeUint(math.Float64bits(real(c)))
|
||||
writeUint(math.Float64bits(imag(c)))
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Although we could treat []byte more efficiently,
|
||||
// JSON values are unlikely to contain them.
|
||||
writeUint(uint64(v.Len()))
|
||||
for i := range v.Len() {
|
||||
write(v.Index(i))
|
||||
}
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
write(v.Elem())
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
for i := range t.NumField() {
|
||||
if sf := t.Field(i); sf.IsExported() {
|
||||
write(v.FieldByIndex(sf.Index))
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if v.Type().Key().Kind() != reflect.String {
|
||||
panic("map with non-string key")
|
||||
}
|
||||
// Sort the keys so the hash is deterministic.
|
||||
keys := v.MapKeys()
|
||||
// Write the length. That distinguishes between, say, two consecutive
|
||||
// maps with disjoint keys from one map that has the items of both.
|
||||
writeUint(uint64(len(keys)))
|
||||
slices.SortFunc(keys, func(x, y reflect.Value) int { return cmp.Compare(x.String(), y.String()) })
|
||||
for _, k := range keys {
|
||||
write(k)
|
||||
write(v.MapIndex(k))
|
||||
}
|
||||
// Ints, uints and floats handled in jsonNumber, at top of function.
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
write(v)
|
||||
}
|
||||
|
||||
// jsonNumber converts a numeric value or a json.Number to a [big.Rat].
|
||||
// If v is not a number, it returns nil, false.
|
||||
func jsonNumber(v reflect.Value) (*big.Rat, bool) {
|
||||
r := new(big.Rat)
|
||||
switch {
|
||||
case !v.IsValid():
|
||||
return nil, false
|
||||
case v.CanInt():
|
||||
r.SetInt64(v.Int())
|
||||
case v.CanUint():
|
||||
r.SetUint64(v.Uint())
|
||||
case v.CanFloat():
|
||||
r.SetFloat64(v.Float())
|
||||
default:
|
||||
jn, ok := v.Interface().(json.Number)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if _, ok := r.SetString(jn.String()); !ok {
|
||||
// This can fail in rare cases; for example, "1e9999999".
|
||||
// That is a valid JSON number, since the spec puts no limit on the size
|
||||
// of the exponent.
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
// jsonType returns a string describing the type of the JSON value,
|
||||
// as described in the JSON Schema specification:
|
||||
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1.
|
||||
// It returns "", false if the value is not valid JSON.
|
||||
func jsonType(v reflect.Value) (string, bool) {
|
||||
if !v.IsValid() {
|
||||
// Not v.IsNil(): a nil []any is still a JSON array.
|
||||
return "null", true
|
||||
}
|
||||
if v.CanInt() || v.CanUint() {
|
||||
return "integer", true
|
||||
}
|
||||
if v.CanFloat() {
|
||||
if _, f := math.Modf(v.Float()); f == 0 {
|
||||
return "integer", true
|
||||
}
|
||||
return "number", true
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return "boolean", true
|
||||
case reflect.String:
|
||||
return "string", true
|
||||
case reflect.Slice, reflect.Array:
|
||||
return "array", true
|
||||
case reflect.Map, reflect.Struct:
|
||||
return "object", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func assert(cond bool, msg string) {
|
||||
if !cond {
|
||||
panic("assertion failed: " + msg)
|
||||
}
|
||||
}
|
||||
|
||||
// marshalStructWithMap marshals its first argument to JSON, treating the field named
|
||||
// mapField as an embedded map. The first argument must be a pointer to
|
||||
// a struct. The underlying type of mapField must be a map[string]any, and it must have
|
||||
// a "-" json tag, meaning it will not be marshaled.
|
||||
//
|
||||
// For example, given this struct:
|
||||
//
|
||||
// type S struct {
|
||||
// A int
|
||||
// Extra map[string] any `json:"-"`
|
||||
// }
|
||||
//
|
||||
// and this value:
|
||||
//
|
||||
// s := S{A: 1, Extra: map[string]any{"B": 2}}
|
||||
//
|
||||
// the call marshalJSONWithMap(s, "Extra") would return
|
||||
//
|
||||
// {"A": 1, "B": 2}
|
||||
//
|
||||
// It is an error if the map contains the same key as another struct field's
|
||||
// JSON name.
|
||||
//
|
||||
// marshalStructWithMap calls json.Marshal on a value of type T, so T must not
|
||||
// have a MarshalJSON method that calls this function, on pain of infinite regress.
|
||||
//
|
||||
// Note that there is a similar function in mcp/util.go, but they are not the same.
|
||||
// Here the function requires `-` json tag, does not clear the mapField map,
|
||||
// and handles embedded struct due to the implementation of jsonNames in this package.
|
||||
//
|
||||
// TODO: avoid this restriction on T by forcing it to marshal in a default way.
|
||||
// See https://go.dev/play/p/EgXKJHxEx_R.
|
||||
func marshalStructWithMap[T any](s *T, mapField string) ([]byte, error) {
|
||||
// Marshal the struct and the map separately, and concatenate the bytes.
|
||||
// This strategy is dramatically less complicated than
|
||||
// constructing a synthetic struct or map with the combined keys.
|
||||
if s == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
s2 := *s
|
||||
vMapField := reflect.ValueOf(&s2).Elem().FieldByName(mapField)
|
||||
mapVal := vMapField.Interface().(map[string]any)
|
||||
|
||||
// Check for duplicates.
|
||||
names := jsonNames(reflect.TypeFor[T]())
|
||||
for key := range mapVal {
|
||||
if names[key] {
|
||||
return nil, fmt.Errorf("map key %q duplicates struct field", key)
|
||||
}
|
||||
}
|
||||
|
||||
structBytes, err := json.Marshal(s2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshalStructWithMap(%+v): %w", s, err)
|
||||
}
|
||||
if len(mapVal) == 0 {
|
||||
return structBytes, nil
|
||||
}
|
||||
mapBytes, err := json.Marshal(mapVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(structBytes) == 2 { // must be "{}"
|
||||
return mapBytes, nil
|
||||
}
|
||||
// "{X}" + "{Y}" => "{X,Y}"
|
||||
res := append(structBytes[:len(structBytes)-1], ',')
|
||||
res = append(res, mapBytes[1:]...)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// unmarshalStructWithMap is the inverse of marshalStructWithMap.
|
||||
// T has the same restrictions as in that function.
|
||||
//
|
||||
// Note that there is a similar function in mcp/util.go, but they are not the same.
|
||||
// Here jsonNames also returns fields from embedded structs, hence this function
|
||||
// handles embedded structs as well.
|
||||
func unmarshalStructWithMap[T any](data []byte, v *T, mapField string) error {
|
||||
// Unmarshal into the struct, ignoring unknown fields.
|
||||
if err := json.Unmarshal(data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
// Unmarshal into the map.
|
||||
m := map[string]any{}
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete from the map the fields of the struct.
|
||||
for n := range jsonNames(reflect.TypeFor[T]()) {
|
||||
delete(m, n)
|
||||
}
|
||||
if len(m) != 0 {
|
||||
reflect.ValueOf(v).Elem().FieldByName(mapField).Set(reflect.ValueOf(m))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var jsonNamesMap sync.Map // from reflect.Type to map[string]bool
|
||||
|
||||
// jsonNames returns the set of JSON object keys that t will marshal into,
|
||||
// including fields from embedded structs in t.
|
||||
// t must be a struct type.
|
||||
//
|
||||
// Note that there is a similar function in mcp/util.go, but they are not the same
|
||||
// Here the function recurses over embedded structs and includes fields from them.
|
||||
func jsonNames(t reflect.Type) map[string]bool {
|
||||
// Lock not necessary: at worst we'll duplicate work.
|
||||
if val, ok := jsonNamesMap.Load(t); ok {
|
||||
return val.(map[string]bool)
|
||||
}
|
||||
m := map[string]bool{}
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
// handle embedded structs
|
||||
if field.Anonymous {
|
||||
fieldType := field.Type
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
for n := range jsonNames(fieldType) {
|
||||
m[n] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
info := fieldJSONInfo(field)
|
||||
if !info.omit {
|
||||
m[info.name] = true
|
||||
}
|
||||
}
|
||||
jsonNamesMap.Store(t, m)
|
||||
return m
|
||||
}
|
||||
|
||||
type jsonInfo struct {
|
||||
omit bool // unexported or first tag element is "-"
|
||||
name string // Go field name or first tag element. Empty if omit is true.
|
||||
settings map[string]bool // "omitempty", "omitzero", etc.
|
||||
}
|
||||
|
||||
// fieldJSONInfo reports information about how encoding/json
|
||||
// handles the given struct field.
|
||||
// If the field is unexported, jsonInfo.omit is true and no other jsonInfo field
|
||||
// is populated.
|
||||
// If the field is exported and has no tag, then name is the field's name and all
|
||||
// other fields are false.
|
||||
// Otherwise, the information is obtained from the tag.
|
||||
func fieldJSONInfo(f reflect.StructField) jsonInfo {
|
||||
if !f.IsExported() {
|
||||
return jsonInfo{omit: true}
|
||||
}
|
||||
info := jsonInfo{name: f.Name}
|
||||
if tag, ok := f.Tag.Lookup("json"); ok {
|
||||
name, rest, found := strings.Cut(tag, ",")
|
||||
// "-" means omit, but "-," means the name is "-"
|
||||
if name == "-" && !found {
|
||||
return jsonInfo{omit: true}
|
||||
}
|
||||
if name != "" {
|
||||
info.name = name
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
info.settings = map[string]bool{}
|
||||
for _, s := range strings.Split(rest, ",") {
|
||||
info.settings[s] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// wrapf wraps *errp with the given formatted message if *errp is not nil.
|
||||
func wrapf(errp *error, format string, args ...any) {
|
||||
if *errp != nil {
|
||||
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
|
||||
}
|
||||
}
|
||||
906
vendor/github.com/google/jsonschema-go/jsonschema/validate.go
generated
vendored
Normal file
906
vendor/github.com/google/jsonschema-go/jsonschema/validate.go
generated
vendored
Normal file
@@ -0,0 +1,906 @@
|
||||
// Copyright 2025 The JSON Schema Go Project Authors. All rights reserved.
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/maphash"
|
||||
"iter"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// The values of the "$schema" keyword for the versions that we can validate.
|
||||
const (
|
||||
draft7SchemaVersion = "http://json-schema.org/draft-07/schema#"
|
||||
draft7SecSchemaVersion = "https://json-schema.org/draft-07/schema#"
|
||||
draft202012SchemaVersion = "https://json-schema.org/draft/2020-12/schema"
|
||||
)
|
||||
|
||||
// isValidSchemaVersion checks if the given schema version is supported
|
||||
func isValidSchemaVersion(version string) bool {
|
||||
return version == "" || version == draft7SchemaVersion || version == draft7SecSchemaVersion || version == draft202012SchemaVersion
|
||||
}
|
||||
|
||||
// Validate validates the instance, which must be a JSON value, against the schema.
|
||||
// It returns nil if validation is successful or an error if it is not.
|
||||
// If the schema type is "object", instance should be a map[string]any.
|
||||
func (rs *Resolved) Validate(instance any) error {
|
||||
if s := rs.root.Schema; !isValidSchemaVersion(s) {
|
||||
return fmt.Errorf("cannot validate version %s, supported versions: draft-07 and draft 2020-12", s)
|
||||
}
|
||||
st := &state{rs: rs}
|
||||
return st.validate(reflect.ValueOf(instance), st.rs.root, nil)
|
||||
}
|
||||
|
||||
// validateDefaults walks the schema tree. If it finds a default, it validates it
|
||||
// against the schema containing it.
|
||||
//
|
||||
// TODO(jba): account for dynamic refs. This algorithm simple-mindedly
|
||||
// treats each schema with a default as its own root.
|
||||
func (rs *Resolved) validateDefaults() error {
|
||||
if s := rs.root.Schema; !isValidSchemaVersion(s) {
|
||||
return fmt.Errorf("cannot validate version %s, supported versions: draft-07 and draft 2020-12", s)
|
||||
}
|
||||
st := &state{rs: rs}
|
||||
for s := range rs.root.all() {
|
||||
// We checked for nil schemas in [Schema.Resolve].
|
||||
assert(s != nil, "nil schema")
|
||||
if s.DynamicRef != "" {
|
||||
return fmt.Errorf("jsonschema: %s: validateDefaults does not support dynamic refs", rs.schemaString(s))
|
||||
}
|
||||
if s.Default != nil {
|
||||
var d any
|
||||
if err := json.Unmarshal(s.Default, &d); err != nil {
|
||||
return fmt.Errorf("unmarshaling default value of schema %s: %w", rs.schemaString(s), err)
|
||||
}
|
||||
if err := st.validate(reflect.ValueOf(d), s, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// state is the state of single call to ResolvedSchema.Validate.
|
||||
type state struct {
|
||||
rs *Resolved
|
||||
// stack holds the schemas from recursive calls to validate.
|
||||
// These are the "dynamic scopes" used to resolve dynamic references.
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#scopes
|
||||
stack []*Schema
|
||||
}
|
||||
|
||||
// validate validates the reflected value of the instance.
|
||||
func (st *state) validate(instance reflect.Value, schema *Schema, callerAnns *annotations) (err error) {
|
||||
defer wrapf(&err, "validating %s", st.rs.schemaString(schema))
|
||||
|
||||
// Maintain a stack for dynamic schema resolution.
|
||||
st.stack = append(st.stack, schema) // push
|
||||
defer func() {
|
||||
st.stack = st.stack[:len(st.stack)-1] // pop
|
||||
}()
|
||||
|
||||
// We checked for nil schemas in [Schema.Resolve].
|
||||
assert(schema != nil, "nil schema")
|
||||
|
||||
// Step through interfaces and pointers.
|
||||
for instance.Kind() == reflect.Pointer || instance.Kind() == reflect.Interface {
|
||||
instance = instance.Elem()
|
||||
}
|
||||
|
||||
schemaInfo := st.rs.resolvedInfos[schema]
|
||||
|
||||
var anns annotations // all the annotations for this call and child calls
|
||||
// $ref: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.1
|
||||
if schema.Ref != "" {
|
||||
if err := st.validate(instance, schemaInfo.resolvedRef, &anns); err != nil {
|
||||
return err
|
||||
}
|
||||
// https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.8.3
|
||||
// "All other properties in a "$ref" object MUST be ignored."
|
||||
if st.rs.draft == draft7 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// type: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.1
|
||||
if schema.Type != "" || schema.Types != nil {
|
||||
gotType, ok := jsonType(instance)
|
||||
if !ok {
|
||||
return fmt.Errorf("type: %v of type %[1]T is not a valid JSON value", instance)
|
||||
}
|
||||
if schema.Type != "" {
|
||||
// "number" subsumes integers
|
||||
if !(gotType == schema.Type ||
|
||||
gotType == "integer" && schema.Type == "number") {
|
||||
return fmt.Errorf("type: %v has type %q, want %q", instance, gotType, schema.Type)
|
||||
}
|
||||
} else {
|
||||
if !(slices.Contains(schema.Types, gotType) || (gotType == "integer" && slices.Contains(schema.Types, "number"))) {
|
||||
return fmt.Errorf("type: %v has type %q, want one of %q",
|
||||
instance, gotType, strings.Join(schema.Types, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
// enum: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.2
|
||||
if schema.Enum != nil {
|
||||
ok := false
|
||||
for _, e := range schema.Enum {
|
||||
if equalValue(reflect.ValueOf(e), instance) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("enum: %v does not equal any of: %v", instance, schema.Enum)
|
||||
}
|
||||
}
|
||||
|
||||
// const: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.3
|
||||
if schema.Const != nil {
|
||||
if !equalValue(reflect.ValueOf(*schema.Const), instance) {
|
||||
return fmt.Errorf("const: %v does not equal %v", instance, *schema.Const)
|
||||
}
|
||||
}
|
||||
|
||||
// numbers: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.2
|
||||
if schema.MultipleOf != nil || schema.Minimum != nil || schema.Maximum != nil || schema.ExclusiveMinimum != nil || schema.ExclusiveMaximum != nil {
|
||||
n, ok := jsonNumber(instance)
|
||||
if ok { // these keywords don't apply to non-numbers
|
||||
if schema.MultipleOf != nil {
|
||||
// TODO: validate MultipleOf as non-zero.
|
||||
// The test suite assumes floats.
|
||||
nf, _ := n.Float64() // don't care if it's exact or not
|
||||
if _, f := math.Modf(nf / *schema.MultipleOf); f != 0 {
|
||||
return fmt.Errorf("multipleOf: %s is not a multiple of %f", n, *schema.MultipleOf)
|
||||
}
|
||||
}
|
||||
|
||||
m := new(big.Rat) // reuse for all of the following
|
||||
cmp := func(f float64) int { return n.Cmp(m.SetFloat64(f)) }
|
||||
|
||||
if schema.Minimum != nil && cmp(*schema.Minimum) < 0 {
|
||||
return fmt.Errorf("minimum: %s is less than %f", n, *schema.Minimum)
|
||||
}
|
||||
if schema.Maximum != nil && cmp(*schema.Maximum) > 0 {
|
||||
return fmt.Errorf("maximum: %s is greater than %f", n, *schema.Maximum)
|
||||
}
|
||||
if schema.ExclusiveMinimum != nil && cmp(*schema.ExclusiveMinimum) <= 0 {
|
||||
return fmt.Errorf("exclusiveMinimum: %s is less than or equal to %f", n, *schema.ExclusiveMinimum)
|
||||
}
|
||||
if schema.ExclusiveMaximum != nil && cmp(*schema.ExclusiveMaximum) >= 0 {
|
||||
return fmt.Errorf("exclusiveMaximum: %s is greater than or equal to %f", n, *schema.ExclusiveMaximum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strings: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.3
|
||||
if instance.Kind() == reflect.String && (schema.MinLength != nil || schema.MaxLength != nil || schema.Pattern != "") {
|
||||
str := instance.String()
|
||||
n := utf8.RuneCountInString(str)
|
||||
if schema.MinLength != nil {
|
||||
if m := *schema.MinLength; n < m {
|
||||
return fmt.Errorf("minLength: %q contains %d Unicode code points, fewer than %d", str, n, m)
|
||||
}
|
||||
}
|
||||
if schema.MaxLength != nil {
|
||||
if m := *schema.MaxLength; n > m {
|
||||
return fmt.Errorf("maxLength: %q contains %d Unicode code points, more than %d", str, n, m)
|
||||
}
|
||||
}
|
||||
|
||||
if schema.Pattern != "" && !schemaInfo.pattern.MatchString(str) {
|
||||
return fmt.Errorf("pattern: %q does not match regular expression %q", str, schema.Pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// $dynamicRef: https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.2
|
||||
if schema.DynamicRef != "" {
|
||||
// The ref behaves lexically or dynamically, but not both.
|
||||
assert((schemaInfo.resolvedDynamicRef == nil) != (schemaInfo.dynamicRefAnchor == ""),
|
||||
"DynamicRef not resolved properly")
|
||||
if schemaInfo.resolvedDynamicRef != nil {
|
||||
// Same as $ref.
|
||||
if err := st.validate(instance, schemaInfo.resolvedDynamicRef, &anns); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Dynamic behavior.
|
||||
// Look for the base of the outermost schema on the stack with this dynamic
|
||||
// anchor. (Yes, outermost: the one farthest from here. This the opposite
|
||||
// of how ordinary dynamic variables behave.)
|
||||
// Why the base of the schema being validated and not the schema itself?
|
||||
// Because the base is the scope for anchors. In fact it's possible to
|
||||
// refer to a schema that is not on the stack, but a child of some base
|
||||
// on the stack.
|
||||
// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
|
||||
var dynamicSchema *Schema
|
||||
for _, s := range st.stack {
|
||||
base := st.rs.resolvedInfos[s].base
|
||||
info, ok := st.rs.resolvedInfos[base].anchors[schemaInfo.dynamicRefAnchor]
|
||||
if ok && info.dynamic {
|
||||
dynamicSchema = info.schema
|
||||
break
|
||||
}
|
||||
}
|
||||
if dynamicSchema == nil {
|
||||
return fmt.Errorf("missing dynamic anchor %q", schemaInfo.dynamicRefAnchor)
|
||||
}
|
||||
if err := st.validate(instance, dynamicSchema, &anns); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logic
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#section-10.2
|
||||
// These must happen before arrays and objects because if they evaluate an item or property,
|
||||
// then the unevaluatedItems/Properties schemas don't apply to it.
|
||||
// See https://json-schema.org/draft/2020-12/json-schema-core#section-11.2, paragraph 4.
|
||||
//
|
||||
// If any of these fail, then validation fails, even if there is an unevaluatedXXX
|
||||
// keyword in the schema. The spec is unclear about this, but that is the intention.
|
||||
|
||||
valid := func(s *Schema, anns *annotations) bool { return st.validate(instance, s, anns) == nil }
|
||||
|
||||
if schema.AllOf != nil {
|
||||
for _, ss := range schema.AllOf {
|
||||
if err := st.validate(instance, ss, &anns); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if schema.AnyOf != nil {
|
||||
// We must visit them all, to collect annotations.
|
||||
var errs []error
|
||||
for _, ss := range schema.AnyOf {
|
||||
if err := st.validate(instance, ss, &anns); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) == len(schema.AnyOf) {
|
||||
return fmt.Errorf("anyOf: did not validate against any of %v:\n%v",
|
||||
schema.AnyOf, errors.Join(errs...))
|
||||
}
|
||||
}
|
||||
if schema.OneOf != nil {
|
||||
// Exactly one.
|
||||
var okSchema *Schema
|
||||
for _, ss := range schema.OneOf {
|
||||
if valid(ss, &anns) {
|
||||
if okSchema != nil {
|
||||
return fmt.Errorf("oneOf: validated against both %v and %v", okSchema, ss)
|
||||
}
|
||||
okSchema = ss
|
||||
}
|
||||
}
|
||||
if okSchema == nil {
|
||||
return fmt.Errorf("oneOf: did not validate against any of %v", schema.OneOf)
|
||||
}
|
||||
}
|
||||
if schema.Not != nil {
|
||||
// Ignore annotations from "not".
|
||||
if valid(schema.Not, nil) {
|
||||
return fmt.Errorf("not: validated against %v", schema.Not)
|
||||
}
|
||||
}
|
||||
if schema.If != nil {
|
||||
var ss *Schema
|
||||
if valid(schema.If, &anns) {
|
||||
ss = schema.Then
|
||||
} else {
|
||||
ss = schema.Else
|
||||
}
|
||||
if ss != nil {
|
||||
if err := st.validate(instance, ss, &anns); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arrays
|
||||
if instance.Kind() == reflect.Array || instance.Kind() == reflect.Slice {
|
||||
// Handle both draft-07 and draft 2020-12
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1
|
||||
// This validate call doesn't collect annotations for the items of the instance; they are separate
|
||||
// instances in their own right.
|
||||
// TODO(jba): if the test suite doesn't cover this case, add a test. For example, nested arrays.
|
||||
if st.rs.draft == draft7 {
|
||||
// For draft-07: additionalItems applies to remaining items after items array.
|
||||
// If items is a Schema or if items is not set, additionalItems should be ignored
|
||||
if schema.ItemsArray != nil {
|
||||
for i, ischema := range schema.ItemsArray {
|
||||
if i >= instance.Len() {
|
||||
break // shorter is OK
|
||||
}
|
||||
if err := st.validate(instance.Index(i), ischema, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
anns.noteEndIndex(min(len(schema.ItemsArray), instance.Len()))
|
||||
if schema.AdditionalItems != nil {
|
||||
for i := len(schema.ItemsArray); i < instance.Len(); i++ {
|
||||
if err := st.validate(instance.Index(i), schema.AdditionalItems, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
anns.allItems = true
|
||||
}
|
||||
} else if schema.Items != nil {
|
||||
for i := 0; i < instance.Len(); i++ {
|
||||
if err := st.validate(instance.Index(i), schema.Items, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Note that all the items in this array have been validated.
|
||||
anns.allItems = true
|
||||
}
|
||||
} else if st.rs.draft == draft2020 {
|
||||
// For draft 2020-12: items applies to remaining items after prefixItems
|
||||
for i, ischema := range schema.PrefixItems {
|
||||
if i >= instance.Len() {
|
||||
break // shorter is OK
|
||||
}
|
||||
if err := st.validate(instance.Index(i), ischema, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
anns.noteEndIndex(min(len(schema.PrefixItems), instance.Len()))
|
||||
if schema.Items != nil {
|
||||
for i := len(schema.PrefixItems); i < instance.Len(); i++ {
|
||||
if err := st.validate(instance.Index(i), schema.Items, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Note that all the items in this array have been validated.
|
||||
anns.allItems = true
|
||||
}
|
||||
}
|
||||
nContains := 0
|
||||
if schema.Contains != nil {
|
||||
for i := range instance.Len() {
|
||||
if err := st.validate(instance.Index(i), schema.Contains, nil); err == nil {
|
||||
nContains++
|
||||
anns.noteIndex(i)
|
||||
}
|
||||
}
|
||||
if nContains == 0 && (schema.MinContains == nil || *schema.MinContains > 0) {
|
||||
return fmt.Errorf("contains: %s does not have an item matching %s", instance, schema.Contains)
|
||||
}
|
||||
}
|
||||
|
||||
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.4
|
||||
// TODO(jba): check that these next four keywords' values are integers.
|
||||
if schema.MinContains != nil && schema.Contains != nil {
|
||||
if m := *schema.MinContains; nContains < m {
|
||||
return fmt.Errorf("minContains: contains validated %d items, less than %d", nContains, m)
|
||||
}
|
||||
}
|
||||
if schema.MaxContains != nil && schema.Contains != nil {
|
||||
if m := *schema.MaxContains; nContains > m {
|
||||
return fmt.Errorf("maxContains: contains validated %d items, greater than %d", nContains, m)
|
||||
}
|
||||
}
|
||||
if schema.MinItems != nil {
|
||||
if m := *schema.MinItems; instance.Len() < m {
|
||||
return fmt.Errorf("minItems: array length %d is less than %d", instance.Len(), m)
|
||||
}
|
||||
}
|
||||
if schema.MaxItems != nil {
|
||||
if m := *schema.MaxItems; instance.Len() > m {
|
||||
return fmt.Errorf("maxItems: array length %d is greater than %d", instance.Len(), m)
|
||||
}
|
||||
}
|
||||
if schema.UniqueItems {
|
||||
if instance.Len() > 1 {
|
||||
// Hash each item and compare the hashes.
|
||||
// If two hashes differ, the items differ.
|
||||
// If two hashes are the same, compare the collisions for equality.
|
||||
// (The same logic as hash table lookup.)
|
||||
// TODO(jba): Use container/hash.Map when it becomes available (https://go.dev/issue/69559),
|
||||
hashes := map[uint64][]int{} // from hash to indices
|
||||
seed := maphash.MakeSeed()
|
||||
for i := range instance.Len() {
|
||||
item := instance.Index(i)
|
||||
var h maphash.Hash
|
||||
h.SetSeed(seed)
|
||||
hashValue(&h, item)
|
||||
hv := h.Sum64()
|
||||
if sames := hashes[hv]; len(sames) > 0 {
|
||||
for _, j := range sames {
|
||||
if equalValue(item, instance.Index(j)) {
|
||||
return fmt.Errorf("uniqueItems: array items %d and %d are equal", i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
hashes[hv] = append(hashes[hv], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#section-11.2
|
||||
if schema.UnevaluatedItems != nil && !anns.allItems {
|
||||
// Apply this subschema to all items in the array that haven't been successfully validated.
|
||||
// That includes validations by subschemas on the same instance, like allOf.
|
||||
for i := anns.endIndex; i < instance.Len(); i++ {
|
||||
if !anns.evaluatedIndexes[i] {
|
||||
if err := st.validate(instance.Index(i), schema.UnevaluatedItems, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
anns.allItems = true
|
||||
}
|
||||
}
|
||||
|
||||
// objects
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2
|
||||
// Validating structs is problematic. See https://github.com/google/jsonschema-go/issues/23.
|
||||
if instance.Kind() == reflect.Struct {
|
||||
return errors.New("cannot validate against a struct; see https://github.com/google/jsonschema-go/issues/23 for details")
|
||||
}
|
||||
if instance.Kind() == reflect.Map {
|
||||
if kt := instance.Type().Key(); kt.Kind() != reflect.String {
|
||||
return fmt.Errorf("map key type %s is not a string", kt)
|
||||
}
|
||||
// Track the evaluated properties for just this schema, to support additionalProperties.
|
||||
// If we used anns here, then we'd be including properties evaluated in subschemas
|
||||
// from allOf, etc., which additionalProperties shouldn't observe.
|
||||
evalProps := map[string]bool{}
|
||||
for prop, subschema := range schema.Properties {
|
||||
val := property(instance, prop)
|
||||
if !val.IsValid() {
|
||||
// It's OK if the instance doesn't have the property.
|
||||
continue
|
||||
}
|
||||
// If the instance is a struct and an optional property has the zero
|
||||
// value, then we could interpret it as present or missing. Be generous:
|
||||
// assume it's missing, and thus always validates successfully.
|
||||
if instance.Kind() == reflect.Struct && val.IsZero() && !schemaInfo.isRequired[prop] {
|
||||
continue
|
||||
}
|
||||
if err := st.validate(val, subschema, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
evalProps[prop] = true
|
||||
}
|
||||
if len(schema.PatternProperties) > 0 {
|
||||
for prop, val := range properties(instance) {
|
||||
// Check every matching pattern.
|
||||
for re, schema := range schemaInfo.patternProperties {
|
||||
if re.MatchString(prop) {
|
||||
if err := st.validate(val, schema, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
evalProps[prop] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if schema.AdditionalProperties != nil {
|
||||
// Special case for a better error message when additional properties is
|
||||
// 'falsy'
|
||||
//
|
||||
// If additionalProperties is {"not":{}} (which is how we
|
||||
// unmarshal "false"), we can produce a better error message that
|
||||
// summarizes all the extra properties. Otherwise, we fall back to the
|
||||
// default validation.
|
||||
//
|
||||
// Note: this is much faster than comparing with falseSchema using Equal.
|
||||
isFalsy := schema.AdditionalProperties.Not != nil && reflect.ValueOf(*schema.AdditionalProperties.Not).IsZero()
|
||||
if isFalsy {
|
||||
var disallowed []string
|
||||
for prop := range properties(instance) {
|
||||
if !evalProps[prop] {
|
||||
disallowed = append(disallowed, prop)
|
||||
}
|
||||
}
|
||||
if len(disallowed) > 0 {
|
||||
return fmt.Errorf("unexpected additional properties %q", disallowed)
|
||||
}
|
||||
} else {
|
||||
// Apply to all properties not handled above.
|
||||
for prop, val := range properties(instance) {
|
||||
if !evalProps[prop] {
|
||||
if err := st.validate(val, schema.AdditionalProperties, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
evalProps[prop] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
anns.noteProperties(evalProps)
|
||||
if schema.PropertyNames != nil {
|
||||
// Note: properties unnecessarily fetches each value. We could define a propertyNames function
|
||||
// if performance ever matters.
|
||||
for prop := range properties(instance) {
|
||||
if err := st.validate(reflect.ValueOf(prop), schema.PropertyNames, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.5
|
||||
var min, max int
|
||||
if schema.MinProperties != nil || schema.MaxProperties != nil {
|
||||
min, max = numPropertiesBounds(instance, schemaInfo.isRequired)
|
||||
}
|
||||
if schema.MinProperties != nil {
|
||||
if n, m := max, *schema.MinProperties; n < m {
|
||||
return fmt.Errorf("minProperties: object has %d properties, less than %d", n, m)
|
||||
}
|
||||
}
|
||||
if schema.MaxProperties != nil {
|
||||
if n, m := min, *schema.MaxProperties; n > m {
|
||||
return fmt.Errorf("maxProperties: object has %d properties, greater than %d", n, m)
|
||||
}
|
||||
}
|
||||
|
||||
hasProperty := func(prop string) bool {
|
||||
return property(instance, prop).IsValid()
|
||||
}
|
||||
|
||||
missingProperties := func(props []string) []string {
|
||||
var missing []string
|
||||
for _, p := range props {
|
||||
if !hasProperty(p) {
|
||||
missing = append(missing, p)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
if schema.Required != nil {
|
||||
if m := missingProperties(schema.Required); len(m) > 0 {
|
||||
return fmt.Errorf("required: missing properties: %q", m)
|
||||
}
|
||||
}
|
||||
|
||||
if st.rs.draft == draft7 {
|
||||
if schema.DependencyStrings != nil {
|
||||
for dprop, dstrings := range schema.DependencyStrings {
|
||||
if hasProperty(dprop) {
|
||||
if m := missingProperties(dstrings); len(m) > 0 {
|
||||
return fmt.Errorf("dependentRequired[%q]: missing properties %q", dprop, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if schema.DependencySchemas != nil {
|
||||
for dprop, dschema := range schema.DependencySchemas {
|
||||
if hasProperty(dprop) {
|
||||
err := st.validate(instance, dschema, &anns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if st.rs.draft == draft2020 {
|
||||
if schema.DependentRequired != nil {
|
||||
// "Validation succeeds if, for each name that appears in both the instance
|
||||
// and as a name within this keyword's value, every item in the corresponding
|
||||
// array is also the name of a property in the instance." §6.5.4
|
||||
for dprop, reqs := range schema.DependentRequired {
|
||||
if hasProperty(dprop) {
|
||||
if m := missingProperties(reqs); len(m) > 0 {
|
||||
return fmt.Errorf("dependentRequired[%q]: missing properties %q", dprop, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.2.4
|
||||
if schema.DependentSchemas != nil {
|
||||
// This does not collect annotations, although it seems like it should.
|
||||
for dprop, ss := range schema.DependentSchemas {
|
||||
if hasProperty(dprop) {
|
||||
// TODO: include dependentSchemas[dprop] in the errors.
|
||||
err := st.validate(instance, ss, &anns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if schema.UnevaluatedProperties != nil && !anns.allProperties {
|
||||
// This looks a lot like AdditionalProperties, but depends on in-place keywords like allOf
|
||||
// in addition to sibling keywords.
|
||||
for prop, val := range properties(instance) {
|
||||
if !anns.evaluatedProperties[prop] {
|
||||
if err := st.validate(val, schema.UnevaluatedProperties, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// The spec says the annotation should be the set of evaluated properties, but we can optimize
|
||||
// by setting a single boolean, since after this succeeds all properties will be validated.
|
||||
// See https://json-schema.slack.com/archives/CT7FF623C/p1745592564381459.
|
||||
anns.allProperties = true
|
||||
}
|
||||
}
|
||||
|
||||
if callerAnns != nil {
|
||||
// Our caller wants to know what we've validated.
|
||||
callerAnns.merge(&anns)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveDynamicRef returns the schema referred to by the argument schema's
|
||||
// $dynamicRef value.
|
||||
// It returns an error if the dynamic reference has no referent.
|
||||
// If there is no $dynamicRef, resolveDynamicRef returns nil, nil.
|
||||
// See https://json-schema.org/draft/2020-12/json-schema-core#section-8.2.3.2.
|
||||
func (st *state) resolveDynamicRef(schema *Schema) (*Schema, error) {
|
||||
if schema.DynamicRef == "" {
|
||||
return nil, nil
|
||||
}
|
||||
info := st.rs.resolvedInfos[schema]
|
||||
// The ref behaves lexically or dynamically, but not both.
|
||||
assert((info.resolvedDynamicRef == nil) != (info.dynamicRefAnchor == ""),
|
||||
"DynamicRef not statically resolved properly")
|
||||
if r := info.resolvedDynamicRef; r != nil {
|
||||
// Same as $ref.
|
||||
return r, nil
|
||||
}
|
||||
// Dynamic behavior.
|
||||
// Look for the base of the outermost schema on the stack with this dynamic
|
||||
// anchor. (Yes, outermost: the one farthest from here. This the opposite
|
||||
// of how ordinary dynamic variables behave.)
|
||||
// Why the base of the schema being validated and not the schema itself?
|
||||
// Because the base is the scope for anchors. In fact it's possible to
|
||||
// refer to a schema that is not on the stack, but a child of some base
|
||||
// on the stack.
|
||||
// For an example, search for "detached" in testdata/draft2020-12/dynamicRef.json.
|
||||
for _, s := range st.stack {
|
||||
base := st.rs.resolvedInfos[s].base
|
||||
info, ok := st.rs.resolvedInfos[base].anchors[info.dynamicRefAnchor]
|
||||
if ok && info.dynamic {
|
||||
return info.schema, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("missing dynamic anchor %q", info.dynamicRefAnchor)
|
||||
}
|
||||
|
||||
// ApplyDefaults modifies an instance by applying the schema's defaults to it. If
|
||||
// a schema or sub-schema has a default, then a corresponding missing instance value
|
||||
// is set to the default.
|
||||
//
|
||||
// The JSON Schema specification does not describe how defaults should be interpreted.
|
||||
// This method honors defaults only on properties, and only those that are not required.
|
||||
// If the instance is a map and the property is missing, the property is added to
|
||||
// the map with the default.
|
||||
// ApplyDefaults does not support structs, because it cannot know whether a field
|
||||
// is missing in the JSON, or was explicitly set to its zero value.
|
||||
//
|
||||
// ApplyDefaults can panic if a default cannot be assigned to a field.
|
||||
//
|
||||
// The argument must be a pointer to the instance.
|
||||
// (In case we decide that top-level defaults are meaningful.)
|
||||
//
|
||||
// It is recommended to first call Resolve with a ValidateDefaults option of true,
|
||||
// then call this method, and lastly call Validate.
|
||||
func (rs *Resolved) ApplyDefaults(instancep any) error {
|
||||
// TODO(jba): consider what defaults on top-level or array instances might mean.
|
||||
// TODO(jba): follow $ref and $dynamicRef
|
||||
st := &state{rs: rs}
|
||||
return st.applyDefaults(reflect.ValueOf(instancep), rs.root)
|
||||
}
|
||||
|
||||
// Recursive helper used by ApplyDefaults. Applies defaults on sub-schemas
|
||||
// of object properties recursively.
|
||||
func (st *state) applyDefaults(instancep reflect.Value, schema *Schema) (err error) {
|
||||
defer wrapf(&err, "applyDefaults: schema %s, instance %v", st.rs.schemaString(schema), instancep)
|
||||
|
||||
schemaInfo := st.rs.resolvedInfos[schema]
|
||||
instance := instancep.Elem()
|
||||
if instance.Kind() == reflect.Interface && instance.IsValid() {
|
||||
// If we unmarshalled into 'any', the default object unmarshalling will be map[string]any.
|
||||
instance = instance.Elem()
|
||||
}
|
||||
if instance.Kind() == reflect.Map || instance.Kind() == reflect.Struct {
|
||||
if instance.Kind() == reflect.Map {
|
||||
if kt := instance.Type().Key(); kt.Kind() != reflect.String {
|
||||
return fmt.Errorf("map key type %s is not a string", kt)
|
||||
}
|
||||
}
|
||||
for prop, subschema := range schema.Properties {
|
||||
// Ignore defaults on required properties. (A required property shouldn't have a default.)
|
||||
if schemaInfo.isRequired[prop] {
|
||||
continue
|
||||
}
|
||||
val := property(instance, prop)
|
||||
switch instance.Kind() {
|
||||
case reflect.Map:
|
||||
// If there is a default for this property, and the map key is missing,
|
||||
// set the map value to the default.
|
||||
if subschema.Default != nil && !val.IsValid() {
|
||||
// Create an lvalue, since map values aren't addressable.
|
||||
lvalue := reflect.New(instance.Type().Elem())
|
||||
if err := json.Unmarshal(subschema.Default, lvalue.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Recurse unconditionally; applyDefaults will only act on object-like values.
|
||||
if err := st.applyDefaults(lvalue, subschema); err != nil {
|
||||
return err
|
||||
}
|
||||
instance.SetMapIndex(reflect.ValueOf(prop), lvalue.Elem())
|
||||
} else if val.IsValid() {
|
||||
// Recurse into an existing sub-instance.
|
||||
// MapIndex returns a non-addressable value; copy into an addressable lvalue, recurse, then set back.
|
||||
lvalue := reflect.New(instance.Type().Elem())
|
||||
// Initialize the lvalue with current value.
|
||||
lvalue.Elem().Set(val)
|
||||
if err := st.applyDefaults(lvalue, subschema); err != nil {
|
||||
return err
|
||||
}
|
||||
instance.SetMapIndex(reflect.ValueOf(prop), lvalue.Elem())
|
||||
} else if schemaHasDefaultsInProperties(subschema) {
|
||||
// Property is missing, but descendants still have some defaults
|
||||
// Create an empty container and recurse to populate
|
||||
elemType := instance.Type().Elem()
|
||||
var child reflect.Value
|
||||
switch elemType.Kind() {
|
||||
case reflect.Interface:
|
||||
child = reflect.ValueOf(map[string]any{})
|
||||
case reflect.Map:
|
||||
child = reflect.MakeMap(elemType)
|
||||
case reflect.Struct:
|
||||
child = reflect.New(elemType).Elem()
|
||||
}
|
||||
if child.IsValid() {
|
||||
lvalue := reflect.New(elemType)
|
||||
lvalue.Elem().Set(child)
|
||||
if err := st.applyDefaults(lvalue, subschema); err != nil {
|
||||
return err
|
||||
}
|
||||
instance.SetMapIndex(reflect.ValueOf(prop), lvalue.Elem())
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
return errors.New("cannot apply defaults to a struct")
|
||||
default:
|
||||
panic(fmt.Sprintf("applyDefaults: property %s: bad value %s of kind %s",
|
||||
prop, instance, instance.Kind()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// schemaHasDefaultsInProperties reports whether s or any descendant schema under
|
||||
// its Properties contains a default. Only walks Properties to match ApplyDefaults semantics.
|
||||
func schemaHasDefaultsInProperties(s *Schema) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
if s.Default != nil {
|
||||
return true
|
||||
}
|
||||
if s.Properties != nil {
|
||||
for _, ss := range s.Properties {
|
||||
if schemaHasDefaultsInProperties(ss) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// property returns the value of the property of v with the given name, or the invalid
|
||||
// reflect.Value if there is none.
|
||||
// If v is a map, the property is the value of the map whose key is name.
|
||||
// If v is a struct, the property is the value of the field with the given name according
|
||||
// to the encoding/json package (see [jsonName]).
|
||||
// If v is anything else, property panics.
|
||||
func property(v reflect.Value, name string) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
return v.MapIndex(reflect.ValueOf(name))
|
||||
case reflect.Struct:
|
||||
props := structPropertiesOf(v.Type())
|
||||
// Ignore nonexistent properties.
|
||||
if sf, ok := props[name]; ok {
|
||||
return v.FieldByIndex(sf.Index)
|
||||
}
|
||||
return reflect.Value{}
|
||||
default:
|
||||
panic(fmt.Sprintf("property(%q): bad value %s of kind %s", name, v, v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// properties returns an iterator over the names and values of all properties
|
||||
// in v, which must be a map or a struct.
|
||||
// If a struct, zero-valued properties that are marked omitempty or omitzero
|
||||
// are excluded.
|
||||
func properties(v reflect.Value) iter.Seq2[string, reflect.Value] {
|
||||
return func(yield func(string, reflect.Value) bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
for k, e := range v.Seq2() {
|
||||
if !yield(k.String(), e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
for name, sf := range structPropertiesOf(v.Type()) {
|
||||
val := v.FieldByIndex(sf.Index)
|
||||
if val.IsZero() {
|
||||
info := fieldJSONInfo(sf)
|
||||
if info.settings["omitempty"] || info.settings["omitzero"] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !yield(name, val) {
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("bad value %s of kind %s", v, v.Kind()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// numPropertiesBounds returns bounds on the number of v's properties.
|
||||
// v must be a map or a struct.
|
||||
// If v is a map, both bounds are the map's size.
|
||||
// If v is a struct, the max is the number of struct properties.
|
||||
// But since we don't know whether a zero value indicates a missing optional property
|
||||
// or not, be generous and use the number of non-zero properties as the min.
|
||||
func numPropertiesBounds(v reflect.Value, isRequired map[string]bool) (int, int) {
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
return v.Len(), v.Len()
|
||||
case reflect.Struct:
|
||||
sp := structPropertiesOf(v.Type())
|
||||
min := 0
|
||||
for prop, sf := range sp {
|
||||
if !v.FieldByIndex(sf.Index).IsZero() || isRequired[prop] {
|
||||
min++
|
||||
}
|
||||
}
|
||||
return min, len(sp)
|
||||
default:
|
||||
panic(fmt.Sprintf("properties: bad value: %s of kind %s", v, v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// A propertyMap is a map from property name to struct field index.
|
||||
type propertyMap = map[string]reflect.StructField
|
||||
|
||||
var structProperties sync.Map // from reflect.Type to propertyMap
|
||||
|
||||
// structPropertiesOf returns the JSON Schema properties for the struct type t.
|
||||
// The caller must not mutate the result.
|
||||
func structPropertiesOf(t reflect.Type) propertyMap {
|
||||
// Mutex not necessary: at worst we'll recompute the same value.
|
||||
if props, ok := structProperties.Load(t); ok {
|
||||
return props.(propertyMap)
|
||||
}
|
||||
props := map[string]reflect.StructField{}
|
||||
for _, sf := range reflect.VisibleFields(t) {
|
||||
if sf.Anonymous {
|
||||
continue
|
||||
}
|
||||
info := fieldJSONInfo(sf)
|
||||
if !info.omit {
|
||||
props[info.name] = sf
|
||||
}
|
||||
}
|
||||
structProperties.Store(t, props)
|
||||
return props
|
||||
}
|
||||
6
vendor/github.com/google/s2a-go/.gitignore
generated
vendored
Normal file
6
vendor/github.com/google/s2a-go/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ignore binaries without extension
|
||||
//example/client/client
|
||||
//example/server/server
|
||||
//internal/v2/fakes2av2_server/fakes2av2_server
|
||||
|
||||
.idea/
|
||||
93
vendor/github.com/google/s2a-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
93
vendor/github.com/google/s2a-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening,
|
||||
offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when the Project
|
||||
Steward has a reasonable belief that an individual's behavior may have a
|
||||
negative impact on the project or its community.
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
We do not believe that all conflict is bad; healthy debate and disagreement
|
||||
often yield positive results. However, it is never okay to be disrespectful or
|
||||
to engage in behavior that violates the project’s code of conduct.
|
||||
|
||||
If you see someone violating the code of conduct, you are encouraged to address
|
||||
the behavior directly with those involved. Many issues can be resolved quickly
|
||||
and easily, and this gives people more control over the outcome of their
|
||||
dispute. If you are unable to resolve the matter for any reason, or if the
|
||||
behavior is threatening or harassing, report it. We are dedicated to providing
|
||||
an environment where participants feel welcome and safe.
|
||||
|
||||
Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the
|
||||
Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to
|
||||
receive and address reported violations of the code of conduct. They will then
|
||||
work with a committee consisting of representatives from the Open Source
|
||||
Programs Office and the Google Open Source Strategy team. If for any reason you
|
||||
are uncomfortable reaching out to the Project Steward, please email
|
||||
opensource@google.com.
|
||||
|
||||
We will investigate every complaint, but you may not receive a direct response.
|
||||
We will use our discretion in determining when and how to follow up on reported
|
||||
incidents, which may range from not taking action to permanent expulsion from
|
||||
the project and project-sponsored spaces. We will notify the accused of the
|
||||
report and provide them an opportunity to discuss it before any action is taken.
|
||||
The identity of the reporter will be omitted from the details of the report
|
||||
supplied to the accused. In potentially harmful situations, such as ongoing
|
||||
harassment or threats to anyone's safety, we may take action without notice.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
|
||||
available at
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
29
vendor/github.com/google/s2a-go/CONTRIBUTING.md
generated
vendored
Normal file
29
vendor/github.com/google/s2a-go/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement (CLA). You (or your employer) retain the copyright to your
|
||||
contribution; this simply gives us permission to use and redistribute your
|
||||
contributions as part of the project. Head over to
|
||||
<https://cla.developers.google.com/> to see your current agreements on file or
|
||||
to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
202
vendor/github.com/google/s2a-go/LICENSE.md
generated
vendored
Normal file
202
vendor/github.com/google/s2a-go/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
14
vendor/github.com/google/s2a-go/README.md
generated
vendored
Normal file
14
vendor/github.com/google/s2a-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Secure Session Agent Client Libraries
|
||||
|
||||
The Secure Session Agent is a service that enables a workload to offload select
|
||||
operations from the mTLS handshake and protects a workload's private key
|
||||
material from exfiltration. Specifically, the workload asks the Secure Session
|
||||
Agent for the TLS configuration to use during the handshake, to perform private
|
||||
key operations, and to validate the peer certificate chain. The Secure Session
|
||||
Agent's client libraries enable applications to communicate with the Secure
|
||||
Session Agent during the TLS handshake, and to encrypt traffic to the peer
|
||||
after the TLS handshake is complete.
|
||||
|
||||
This repository contains the source code for the Secure Session Agent's Go
|
||||
client libraries, which allow gRPC and HTTP Go applications to use the Secure Session
|
||||
Agent.
|
||||
167
vendor/github.com/google/s2a-go/fallback/s2a_fallback.go
generated
vendored
Normal file
167
vendor/github.com/google/s2a-go/fallback/s2a_fallback.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package fallback provides default implementations of fallback options when S2A fails.
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
const (
|
||||
alpnProtoStrH2 = "h2"
|
||||
alpnProtoStrHTTP = "http/1.1"
|
||||
defaultHTTPSPort = "443"
|
||||
)
|
||||
|
||||
// FallbackTLSConfigGRPC is a tls.Config used by the DefaultFallbackClientHandshakeFunc function.
|
||||
// It supports GRPC use case, thus the alpn is set to 'h2'.
|
||||
var FallbackTLSConfigGRPC = tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ClientSessionCache: nil,
|
||||
NextProtos: []string{alpnProtoStrH2},
|
||||
}
|
||||
|
||||
// FallbackTLSConfigHTTP is a tls.Config used by the DefaultFallbackDialerAndAddress func.
|
||||
// It supports the HTTP use case and the alpn is set to both 'http/1.1' and 'h2'.
|
||||
var FallbackTLSConfigHTTP = tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ClientSessionCache: nil,
|
||||
NextProtos: []string{alpnProtoStrH2, alpnProtoStrHTTP},
|
||||
}
|
||||
|
||||
// ClientHandshake establishes a TLS connection and returns it, plus its auth info.
|
||||
// Inputs:
|
||||
//
|
||||
// targetServer: the server attempted with S2A.
|
||||
// conn: the tcp connection to the server at address targetServer that was passed into S2A's ClientHandshake func.
|
||||
// If fallback is successful, the `conn` should be closed.
|
||||
// err: the error encountered when performing the client-side TLS handshake with S2A.
|
||||
type ClientHandshake func(ctx context.Context, targetServer string, conn net.Conn, err error) (net.Conn, credentials.AuthInfo, error)
|
||||
|
||||
// DefaultFallbackClientHandshakeFunc returns a ClientHandshake function,
|
||||
// which establishes a TLS connection to the provided fallbackAddr, returns the new connection and its auth info.
|
||||
// Example use:
|
||||
//
|
||||
// transportCreds, _ = s2a.NewClientCreds(&s2a.ClientOptions{
|
||||
// S2AAddress: s2aAddress,
|
||||
// FallbackOpts: &s2a.FallbackOptions{ // optional
|
||||
// FallbackClientHandshakeFunc: fallback.DefaultFallbackClientHandshakeFunc(fallbackAddr),
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// The fallback server's certificate must be verifiable using OS root store.
|
||||
// The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
|
||||
// it uses default port 443.
|
||||
// In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
|
||||
// and min TLS version is set to 1.3.
|
||||
func DefaultFallbackClientHandshakeFunc(fallbackAddr string) (ClientHandshake, error) {
|
||||
var fallbackDialer = tls.Dialer{Config: &FallbackTLSConfigGRPC}
|
||||
return defaultFallbackClientHandshakeFuncInternal(fallbackAddr, fallbackDialer.DialContext)
|
||||
}
|
||||
|
||||
func defaultFallbackClientHandshakeFuncInternal(fallbackAddr string, dialContextFunc func(context.Context, string, string) (net.Conn, error)) (ClientHandshake, error) {
|
||||
fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
|
||||
if err != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, targetServer string, conn net.Conn, s2aErr error) (net.Conn, credentials.AuthInfo, error) {
|
||||
fbConn, fbErr := dialContextFunc(ctx, "tcp", fallbackServerAddr)
|
||||
if fbErr != nil {
|
||||
grpclog.Infof("dialing to fallback server %s failed: %v", fallbackServerAddr, fbErr)
|
||||
return nil, nil, fmt.Errorf("dialing to fallback server %s failed: %v; S2A client handshake with %s error: %w", fallbackServerAddr, fbErr, targetServer, s2aErr)
|
||||
}
|
||||
|
||||
tc, success := fbConn.(*tls.Conn)
|
||||
if !success {
|
||||
grpclog.Infof("the connection with fallback server is expected to be tls but isn't")
|
||||
return nil, nil, fmt.Errorf("the connection with fallback server is expected to be tls but isn't; S2A client handshake with %s error: %w", targetServer, s2aErr)
|
||||
}
|
||||
|
||||
tlsInfo := credentials.TLSInfo{
|
||||
State: tc.ConnectionState(),
|
||||
CommonAuthInfo: credentials.CommonAuthInfo{
|
||||
SecurityLevel: credentials.PrivacyAndIntegrity,
|
||||
},
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("ConnectionState.NegotiatedProtocol: %v", tc.ConnectionState().NegotiatedProtocol)
|
||||
grpclog.Infof("ConnectionState.HandshakeComplete: %v", tc.ConnectionState().HandshakeComplete)
|
||||
grpclog.Infof("ConnectionState.ServerName: %v", tc.ConnectionState().ServerName)
|
||||
}
|
||||
conn.Close()
|
||||
return fbConn, tlsInfo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DefaultFallbackDialerAndAddress returns a TLS dialer and the network address to dial.
|
||||
// Example use:
|
||||
//
|
||||
// fallbackDialer, fallbackServerAddr := fallback.DefaultFallbackDialerAndAddress(fallbackAddr)
|
||||
// dialTLSContext := s2a.NewS2aDialTLSContextFunc(&s2a.ClientOptions{
|
||||
// S2AAddress: s2aAddress, // required
|
||||
// FallbackOpts: &s2a.FallbackOptions{
|
||||
// FallbackDialer: &s2a.FallbackDialer{
|
||||
// Dialer: fallbackDialer,
|
||||
// ServerAddr: fallbackServerAddr,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// The fallback server's certificate should be verifiable using OS root store.
|
||||
// The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
|
||||
// it uses default port 443.
|
||||
// In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
|
||||
// and min TLS version is set to 1.3.
|
||||
func DefaultFallbackDialerAndAddress(fallbackAddr string) (*tls.Dialer, string, error) {
|
||||
fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
|
||||
if err != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
return &tls.Dialer{Config: &FallbackTLSConfigHTTP}, fallbackServerAddr, nil
|
||||
}
|
||||
|
||||
func processFallbackAddr(fallbackAddr string) (string, error) {
|
||||
var fallbackServerAddr string
|
||||
var err error
|
||||
|
||||
if fallbackAddr == "" {
|
||||
return "", fmt.Errorf("empty fallback address")
|
||||
}
|
||||
_, _, err = net.SplitHostPort(fallbackAddr)
|
||||
if err != nil {
|
||||
// fallbackAddr does not have port suffix
|
||||
fallbackServerAddr = net.JoinHostPort(fallbackAddr, defaultHTTPSPort)
|
||||
} else {
|
||||
// FallbackServerAddr already has port suffix
|
||||
fallbackServerAddr = fallbackAddr
|
||||
}
|
||||
return fallbackServerAddr, nil
|
||||
}
|
||||
119
vendor/github.com/google/s2a-go/internal/authinfo/authinfo.go
generated
vendored
Normal file
119
vendor/github.com/google/s2a-go/internal/authinfo/authinfo.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package authinfo provides authentication and authorization information that
|
||||
// results from the TLS handshake.
|
||||
package authinfo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
contextpb "github.com/google/s2a-go/internal/proto/s2a_context_go_proto"
|
||||
grpcpb "github.com/google/s2a-go/internal/proto/s2a_go_proto"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var _ credentials.AuthInfo = (*S2AAuthInfo)(nil)
|
||||
|
||||
const s2aAuthType = "s2a"
|
||||
|
||||
// S2AAuthInfo exposes authentication and authorization information from the
|
||||
// S2A session result to the gRPC stack.
|
||||
type S2AAuthInfo struct {
|
||||
s2aContext *contextpb.S2AContext
|
||||
commonAuthInfo credentials.CommonAuthInfo
|
||||
}
|
||||
|
||||
// NewS2AAuthInfo returns a new S2AAuthInfo object from the S2A session result.
|
||||
func NewS2AAuthInfo(result *grpcpb.SessionResult) (credentials.AuthInfo, error) {
|
||||
return newS2AAuthInfo(result)
|
||||
}
|
||||
|
||||
func newS2AAuthInfo(result *grpcpb.SessionResult) (*S2AAuthInfo, error) {
|
||||
if result == nil {
|
||||
return nil, errors.New("NewS2aAuthInfo given nil session result")
|
||||
}
|
||||
return &S2AAuthInfo{
|
||||
s2aContext: &contextpb.S2AContext{
|
||||
ApplicationProtocol: result.GetApplicationProtocol(),
|
||||
TlsVersion: result.GetState().GetTlsVersion(),
|
||||
Ciphersuite: result.GetState().GetTlsCiphersuite(),
|
||||
PeerIdentity: result.GetPeerIdentity(),
|
||||
LocalIdentity: result.GetLocalIdentity(),
|
||||
PeerCertFingerprint: result.GetPeerCertFingerprint(),
|
||||
LocalCertFingerprint: result.GetLocalCertFingerprint(),
|
||||
IsHandshakeResumed: result.GetState().GetIsHandshakeResumed(),
|
||||
},
|
||||
commonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthType returns the authentication type.
|
||||
func (s *S2AAuthInfo) AuthType() string {
|
||||
return s2aAuthType
|
||||
}
|
||||
|
||||
// ApplicationProtocol returns the application protocol, e.g. "grpc".
|
||||
func (s *S2AAuthInfo) ApplicationProtocol() string {
|
||||
return s.s2aContext.GetApplicationProtocol()
|
||||
}
|
||||
|
||||
// TLSVersion returns the TLS version negotiated during the handshake.
|
||||
func (s *S2AAuthInfo) TLSVersion() commonpb.TLSVersion {
|
||||
return s.s2aContext.GetTlsVersion()
|
||||
}
|
||||
|
||||
// Ciphersuite returns the ciphersuite negotiated during the handshake.
|
||||
func (s *S2AAuthInfo) Ciphersuite() commonpb.Ciphersuite {
|
||||
return s.s2aContext.GetCiphersuite()
|
||||
}
|
||||
|
||||
// PeerIdentity returns the authenticated identity of the peer.
|
||||
func (s *S2AAuthInfo) PeerIdentity() *commonpb.Identity {
|
||||
return s.s2aContext.GetPeerIdentity()
|
||||
}
|
||||
|
||||
// LocalIdentity returns the local identity of the application used during
|
||||
// session setup.
|
||||
func (s *S2AAuthInfo) LocalIdentity() *commonpb.Identity {
|
||||
return s.s2aContext.GetLocalIdentity()
|
||||
}
|
||||
|
||||
// PeerCertFingerprint returns the SHA256 hash of the peer certificate used in
|
||||
// the S2A handshake.
|
||||
func (s *S2AAuthInfo) PeerCertFingerprint() []byte {
|
||||
return s.s2aContext.GetPeerCertFingerprint()
|
||||
}
|
||||
|
||||
// LocalCertFingerprint returns the SHA256 hash of the local certificate used
|
||||
// in the S2A handshake.
|
||||
func (s *S2AAuthInfo) LocalCertFingerprint() []byte {
|
||||
return s.s2aContext.GetLocalCertFingerprint()
|
||||
}
|
||||
|
||||
// IsHandshakeResumed returns true if a cached session was used to resume
|
||||
// the handshake.
|
||||
func (s *S2AAuthInfo) IsHandshakeResumed() bool {
|
||||
return s.s2aContext.GetIsHandshakeResumed()
|
||||
}
|
||||
|
||||
// SecurityLevel returns the security level of the connection.
|
||||
func (s *S2AAuthInfo) SecurityLevel() credentials.SecurityLevel {
|
||||
return s.commonAuthInfo.SecurityLevel
|
||||
}
|
||||
438
vendor/github.com/google/s2a-go/internal/handshaker/handshaker.go
generated
vendored
Normal file
438
vendor/github.com/google/s2a-go/internal/handshaker/handshaker.go
generated
vendored
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package handshaker communicates with the S2A handshaker service.
|
||||
package handshaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/google/s2a-go/internal/authinfo"
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
s2apb "github.com/google/s2a-go/internal/proto/s2a_go_proto"
|
||||
"github.com/google/s2a-go/internal/record"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
grpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
var (
|
||||
// appProtocol contains the application protocol accepted by the handshaker.
|
||||
appProtocol = "grpc"
|
||||
// frameLimit is the maximum size of a frame in bytes.
|
||||
frameLimit = 1024 * 64
|
||||
// peerNotRespondingError is the error thrown when the peer doesn't respond.
|
||||
errPeerNotResponding = errors.New("peer is not responding and re-connection should be attempted")
|
||||
)
|
||||
|
||||
// Handshaker defines a handshaker interface.
|
||||
type Handshaker interface {
|
||||
// ClientHandshake starts and completes a TLS handshake from the client side,
|
||||
// and returns a secure connection along with additional auth information.
|
||||
ClientHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
|
||||
// ServerHandshake starts and completes a TLS handshake from the server side,
|
||||
// and returns a secure connection along with additional auth information.
|
||||
ServerHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
|
||||
// Close terminates the Handshaker. It should be called when the handshake
|
||||
// is complete.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ClientHandshakerOptions contains the options needed to configure the S2A
|
||||
// handshaker service on the client-side.
|
||||
type ClientHandshakerOptions struct {
|
||||
// MinTLSVersion specifies the min TLS version supported by the client.
|
||||
MinTLSVersion commonpb.TLSVersion
|
||||
// MaxTLSVersion specifies the max TLS version supported by the client.
|
||||
MaxTLSVersion commonpb.TLSVersion
|
||||
// TLSCiphersuites is the ordered list of ciphersuites supported by the
|
||||
// client.
|
||||
TLSCiphersuites []commonpb.Ciphersuite
|
||||
// TargetIdentities contains a list of allowed server identities. One of the
|
||||
// target identities should match the peer identity in the handshake
|
||||
// result; otherwise, the handshake fails.
|
||||
TargetIdentities []*commonpb.Identity
|
||||
// LocalIdentity is the local identity of the client application. If none is
|
||||
// provided, then the S2A will choose the default identity.
|
||||
LocalIdentity *commonpb.Identity
|
||||
// TargetName is the allowed server name, which may be used for server
|
||||
// authorization check by the S2A if it is provided.
|
||||
TargetName string
|
||||
// EnsureProcessSessionTickets allows users to wait and ensure that all
|
||||
// available session tickets are sent to S2A before a process completes.
|
||||
EnsureProcessSessionTickets *sync.WaitGroup
|
||||
}
|
||||
|
||||
// ServerHandshakerOptions contains the options needed to configure the S2A
|
||||
// handshaker service on the server-side.
|
||||
type ServerHandshakerOptions struct {
|
||||
// MinTLSVersion specifies the min TLS version supported by the server.
|
||||
MinTLSVersion commonpb.TLSVersion
|
||||
// MaxTLSVersion specifies the max TLS version supported by the server.
|
||||
MaxTLSVersion commonpb.TLSVersion
|
||||
// TLSCiphersuites is the ordered list of ciphersuites supported by the
|
||||
// server.
|
||||
TLSCiphersuites []commonpb.Ciphersuite
|
||||
// LocalIdentities is the list of local identities that may be assumed by
|
||||
// the server. If no local identity is specified, then the S2A chooses a
|
||||
// default local identity.
|
||||
LocalIdentities []*commonpb.Identity
|
||||
}
|
||||
|
||||
// s2aHandshaker performs a TLS handshake using the S2A handshaker service.
|
||||
type s2aHandshaker struct {
|
||||
// stream is used to communicate with the S2A handshaker service.
|
||||
stream s2apb.S2AService_SetUpSessionClient
|
||||
// conn is the connection to the peer.
|
||||
conn net.Conn
|
||||
// clientOpts should be non-nil iff the handshaker is client-side.
|
||||
clientOpts *ClientHandshakerOptions
|
||||
// serverOpts should be non-nil iff the handshaker is server-side.
|
||||
serverOpts *ServerHandshakerOptions
|
||||
// isClient determines if the handshaker is client or server side.
|
||||
isClient bool
|
||||
// hsAddr stores the address of the S2A handshaker service.
|
||||
hsAddr string
|
||||
// tokenManager manages access tokens for authenticating to S2A.
|
||||
tokenManager tokenmanager.AccessTokenManager
|
||||
// localIdentities is the set of local identities for whom the
|
||||
// tokenManager should fetch a token when preparing a request to be
|
||||
// sent to S2A.
|
||||
localIdentities []*commonpb.Identity
|
||||
}
|
||||
|
||||
// NewClientHandshaker creates an s2aHandshaker instance that performs a
|
||||
// client-side TLS handshake using the S2A handshaker service.
|
||||
func NewClientHandshaker(ctx context.Context, conn *grpc.ClientConn, c net.Conn, hsAddr string, opts *ClientHandshakerOptions) (Handshaker, error) {
|
||||
stream, err := s2apb.NewS2AServiceClient(conn).SetUpSession(ctx, grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
grpclog.Infof("failed to create single token access token manager: %v", err)
|
||||
}
|
||||
return newClientHandshaker(stream, c, hsAddr, opts, tokenManager), nil
|
||||
}
|
||||
|
||||
func newClientHandshaker(stream s2apb.S2AService_SetUpSessionClient, c net.Conn, hsAddr string, opts *ClientHandshakerOptions, tokenManager tokenmanager.AccessTokenManager) *s2aHandshaker {
|
||||
var localIdentities []*commonpb.Identity
|
||||
if opts != nil {
|
||||
localIdentities = []*commonpb.Identity{opts.LocalIdentity}
|
||||
}
|
||||
return &s2aHandshaker{
|
||||
stream: stream,
|
||||
conn: c,
|
||||
clientOpts: opts,
|
||||
isClient: true,
|
||||
hsAddr: hsAddr,
|
||||
tokenManager: tokenManager,
|
||||
localIdentities: localIdentities,
|
||||
}
|
||||
}
|
||||
|
||||
// NewServerHandshaker creates an s2aHandshaker instance that performs a
|
||||
// server-side TLS handshake using the S2A handshaker service.
|
||||
func NewServerHandshaker(ctx context.Context, conn *grpc.ClientConn, c net.Conn, hsAddr string, opts *ServerHandshakerOptions) (Handshaker, error) {
|
||||
stream, err := s2apb.NewS2AServiceClient(conn).SetUpSession(ctx, grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
grpclog.Infof("failed to create single token access token manager: %v", err)
|
||||
}
|
||||
return newServerHandshaker(stream, c, hsAddr, opts, tokenManager), nil
|
||||
}
|
||||
|
||||
func newServerHandshaker(stream s2apb.S2AService_SetUpSessionClient, c net.Conn, hsAddr string, opts *ServerHandshakerOptions, tokenManager tokenmanager.AccessTokenManager) *s2aHandshaker {
|
||||
var localIdentities []*commonpb.Identity
|
||||
if opts != nil {
|
||||
localIdentities = opts.LocalIdentities
|
||||
}
|
||||
return &s2aHandshaker{
|
||||
stream: stream,
|
||||
conn: c,
|
||||
serverOpts: opts,
|
||||
isClient: false,
|
||||
hsAddr: hsAddr,
|
||||
tokenManager: tokenManager,
|
||||
localIdentities: localIdentities,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientHandshake performs a client-side TLS handshake using the S2A handshaker
|
||||
// service. When complete, returns a TLS connection.
|
||||
func (h *s2aHandshaker) ClientHandshake(_ context.Context) (net.Conn, credentials.AuthInfo, error) {
|
||||
if !h.isClient {
|
||||
return nil, nil, errors.New("only handshakers created using NewClientHandshaker can perform a client-side handshake")
|
||||
}
|
||||
// Extract the hostname from the target name. The target name is assumed to be an authority.
|
||||
hostname, _, err := net.SplitHostPort(h.clientOpts.TargetName)
|
||||
if err != nil {
|
||||
// If the target name had no host port or could not be parsed, use it as is.
|
||||
hostname = h.clientOpts.TargetName
|
||||
}
|
||||
|
||||
// Prepare a client start message to send to the S2A handshaker service.
|
||||
req := &s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_ClientStart{
|
||||
ClientStart: &s2apb.ClientSessionStartReq{
|
||||
ApplicationProtocols: []string{appProtocol},
|
||||
MinTlsVersion: h.clientOpts.MinTLSVersion,
|
||||
MaxTlsVersion: h.clientOpts.MaxTLSVersion,
|
||||
TlsCiphersuites: h.clientOpts.TLSCiphersuites,
|
||||
TargetIdentities: h.clientOpts.TargetIdentities,
|
||||
LocalIdentity: h.clientOpts.LocalIdentity,
|
||||
TargetName: hostname,
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
}
|
||||
conn, result, err := h.setUpSession(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authInfo, err := authinfo.NewS2AAuthInfo(result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, authInfo, nil
|
||||
}
|
||||
|
||||
// ServerHandshake performs a server-side TLS handshake using the S2A handshaker
|
||||
// service. When complete, returns a TLS connection.
|
||||
func (h *s2aHandshaker) ServerHandshake(_ context.Context) (net.Conn, credentials.AuthInfo, error) {
|
||||
if h.isClient {
|
||||
return nil, nil, errors.New("only handshakers created using NewServerHandshaker can perform a server-side handshake")
|
||||
}
|
||||
p := make([]byte, frameLimit)
|
||||
n, err := h.conn.Read(p)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Prepare a server start message to send to the S2A handshaker service.
|
||||
req := &s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_ServerStart{
|
||||
ServerStart: &s2apb.ServerSessionStartReq{
|
||||
ApplicationProtocols: []string{appProtocol},
|
||||
MinTlsVersion: h.serverOpts.MinTLSVersion,
|
||||
MaxTlsVersion: h.serverOpts.MaxTLSVersion,
|
||||
TlsCiphersuites: h.serverOpts.TLSCiphersuites,
|
||||
LocalIdentities: h.serverOpts.LocalIdentities,
|
||||
InBytes: p[:n],
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
}
|
||||
conn, result, err := h.setUpSession(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authInfo, err := authinfo.NewS2AAuthInfo(result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, authInfo, nil
|
||||
}
|
||||
|
||||
// setUpSession proxies messages between the peer and the S2A handshaker
|
||||
// service.
|
||||
func (h *s2aHandshaker) setUpSession(req *s2apb.SessionReq) (net.Conn, *s2apb.SessionResult, error) {
|
||||
resp, err := h.accessHandshakerService(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Check if the returned status is an error.
|
||||
if resp.GetStatus() != nil {
|
||||
if got, want := resp.GetStatus().Code, uint32(codes.OK); got != want {
|
||||
return nil, nil, fmt.Errorf("%v", resp.GetStatus().Details)
|
||||
}
|
||||
}
|
||||
// Calculate the extra unread bytes from the Session. Attempting to consume
|
||||
// more than the bytes sent will throw an error.
|
||||
var extra []byte
|
||||
if req.GetServerStart() != nil {
|
||||
if resp.GetBytesConsumed() > uint32(len(req.GetServerStart().GetInBytes())) {
|
||||
return nil, nil, errors.New("handshaker service consumed bytes value is out-of-bounds")
|
||||
}
|
||||
extra = req.GetServerStart().GetInBytes()[resp.GetBytesConsumed():]
|
||||
}
|
||||
result, extra, err := h.processUntilDone(resp, extra)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if result.GetLocalIdentity() == nil {
|
||||
return nil, nil, errors.New("local identity must be populated in session result")
|
||||
}
|
||||
|
||||
// Create a new TLS record protocol using the Session Result.
|
||||
newConn, err := record.NewConn(&record.ConnParameters{
|
||||
NetConn: h.conn,
|
||||
Ciphersuite: result.GetState().GetTlsCiphersuite(),
|
||||
TLSVersion: result.GetState().GetTlsVersion(),
|
||||
InTrafficSecret: result.GetState().GetInKey(),
|
||||
OutTrafficSecret: result.GetState().GetOutKey(),
|
||||
UnusedBuf: extra,
|
||||
InSequence: result.GetState().GetInSequence(),
|
||||
OutSequence: result.GetState().GetOutSequence(),
|
||||
HSAddr: h.hsAddr,
|
||||
ConnectionID: result.GetState().GetConnectionId(),
|
||||
LocalIdentity: result.GetLocalIdentity(),
|
||||
EnsureProcessSessionTickets: h.ensureProcessSessionTickets(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return newConn, result, nil
|
||||
}
|
||||
|
||||
func (h *s2aHandshaker) ensureProcessSessionTickets() *sync.WaitGroup {
|
||||
if h.clientOpts == nil {
|
||||
return nil
|
||||
}
|
||||
return h.clientOpts.EnsureProcessSessionTickets
|
||||
}
|
||||
|
||||
// accessHandshakerService sends the session request to the S2A handshaker
|
||||
// service and returns the session response.
|
||||
func (h *s2aHandshaker) accessHandshakerService(req *s2apb.SessionReq) (*s2apb.SessionResp, error) {
|
||||
if err := h.stream.Send(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := h.stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// processUntilDone continues proxying messages between the peer and the S2A
|
||||
// handshaker service until the handshaker service returns the SessionResult at
|
||||
// the end of the handshake or an error occurs.
|
||||
func (h *s2aHandshaker) processUntilDone(resp *s2apb.SessionResp, unusedBytes []byte) (*s2apb.SessionResult, []byte, error) {
|
||||
for {
|
||||
if len(resp.OutFrames) > 0 {
|
||||
if _, err := h.conn.Write(resp.OutFrames); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if resp.Result != nil {
|
||||
return resp.Result, unusedBytes, nil
|
||||
}
|
||||
buf := make([]byte, frameLimit)
|
||||
n, err := h.conn.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, nil, err
|
||||
}
|
||||
// If there is nothing to send to the handshaker service and nothing is
|
||||
// received from the peer, then we are stuck. This covers the case when
|
||||
// the peer is not responding. Note that handshaker service connection
|
||||
// issues are caught in accessHandshakerService before we even get
|
||||
// here.
|
||||
if len(resp.OutFrames) == 0 && n == 0 {
|
||||
return nil, nil, errPeerNotResponding
|
||||
}
|
||||
// Append extra bytes from the previous interaction with the handshaker
|
||||
// service with the current buffer read from conn.
|
||||
p := append(unusedBytes, buf[:n]...)
|
||||
// From here on, p and unusedBytes point to the same slice.
|
||||
resp, err = h.accessHandshakerService(&s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_Next{
|
||||
Next: &s2apb.SessionNextReq{
|
||||
InBytes: p,
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Cache the local identity returned by S2A, if it is populated. This
|
||||
// overwrites any existing local identities. This is done because, once the
|
||||
// S2A has selected a local identity, then only that local identity should
|
||||
// be asserted in future requests until the end of the current handshake.
|
||||
if resp.GetLocalIdentity() != nil {
|
||||
h.localIdentities = []*commonpb.Identity{resp.GetLocalIdentity()}
|
||||
}
|
||||
|
||||
// Set unusedBytes based on the handshaker service response.
|
||||
if resp.GetBytesConsumed() > uint32(len(p)) {
|
||||
return nil, nil, errors.New("handshaker service consumed bytes value is out-of-bounds")
|
||||
}
|
||||
unusedBytes = p[resp.GetBytesConsumed():]
|
||||
}
|
||||
}
|
||||
|
||||
// Close shuts down the handshaker and the stream to the S2A handshaker service
|
||||
// when the handshake is complete. It should be called when the caller obtains
|
||||
// the secure connection at the end of the handshake.
|
||||
func (h *s2aHandshaker) Close() error {
|
||||
return h.stream.CloseSend()
|
||||
}
|
||||
|
||||
func (h *s2aHandshaker) getAuthMechanisms() []*s2apb.AuthenticationMechanism {
|
||||
if h.tokenManager == nil {
|
||||
return nil
|
||||
}
|
||||
// First handle the special case when no local identities have been provided
|
||||
// by the application. In this case, an AuthenticationMechanism with no local
|
||||
// identity will be sent.
|
||||
if len(h.localIdentities) == 0 {
|
||||
token, err := h.tokenManager.DefaultToken()
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for empty local identity: %v", err)
|
||||
return nil
|
||||
}
|
||||
return []*s2apb.AuthenticationMechanism{
|
||||
{
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Next, handle the case where the application (or the S2A) has provided
|
||||
// one or more local identities.
|
||||
var authMechanisms []*s2apb.AuthenticationMechanism
|
||||
for _, localIdentity := range h.localIdentities {
|
||||
token, err := h.tokenManager.Token(localIdentity)
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for local identity %v: %v", localIdentity, err)
|
||||
continue
|
||||
}
|
||||
|
||||
authMechanism := &s2apb.AuthenticationMechanism{
|
||||
Identity: localIdentity,
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
authMechanisms = append(authMechanisms, authMechanism)
|
||||
}
|
||||
return authMechanisms
|
||||
}
|
||||
66
vendor/github.com/google/s2a-go/internal/handshaker/service/service.go
generated
vendored
Normal file
66
vendor/github.com/google/s2a-go/internal/handshaker/service/service.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package service is a utility for calling the S2A handshaker service.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var (
|
||||
// mu guards hsConnMap and hsDialer.
|
||||
mu sync.Mutex
|
||||
// hsConnMap represents a mapping from an S2A handshaker service address
|
||||
// to a corresponding connection to an S2A handshaker service instance.
|
||||
hsConnMap = make(map[string]*grpc.ClientConn)
|
||||
// hsDialer will be reassigned in tests.
|
||||
hsDialer = grpc.DialContext
|
||||
)
|
||||
|
||||
// Dial dials the S2A handshaker service. If a connection has already been
|
||||
// established, this function returns it. Otherwise, a new connection is
|
||||
// created.
|
||||
func Dial(ctx context.Context, handshakerServiceAddress string, transportCreds credentials.TransportCredentials) (*grpc.ClientConn, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
hsConn, ok := hsConnMap[handshakerServiceAddress]
|
||||
if !ok {
|
||||
// Create a new connection to the S2A handshaker service. Note that
|
||||
// this connection stays open until the application is closed.
|
||||
var grpcOpts []grpc.DialOption
|
||||
if transportCreds != nil {
|
||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(transportCreds))
|
||||
} else {
|
||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
var err error
|
||||
hsConn, err = hsDialer(ctx, handshakerServiceAddress, grpcOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hsConnMap[handshakerServiceAddress] = hsConn
|
||||
}
|
||||
return hsConn, nil
|
||||
}
|
||||
388
vendor/github.com/google/s2a-go/internal/proto/common_go_proto/common.pb.go
generated
vendored
Normal file
388
vendor/github.com/google/s2a-go/internal/proto/common_go_proto/common.pb.go
generated
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.21.12
|
||||
// source: internal/proto/common/common.proto
|
||||
|
||||
package common_go_proto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// The ciphersuites supported by S2A. The name determines the confidentiality,
|
||||
// and authentication ciphers as well as the hash algorithm used for PRF in
|
||||
// TLS 1.2 or HKDF in TLS 1.3. Thus, the components of the name are:
|
||||
// - AEAD -- for encryption and authentication, e.g., AES_128_GCM.
|
||||
// - Hash algorithm -- used in PRF or HKDF, e.g., SHA256.
|
||||
type Ciphersuite int32
|
||||
|
||||
const (
|
||||
Ciphersuite_AES_128_GCM_SHA256 Ciphersuite = 0
|
||||
Ciphersuite_AES_256_GCM_SHA384 Ciphersuite = 1
|
||||
Ciphersuite_CHACHA20_POLY1305_SHA256 Ciphersuite = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Ciphersuite.
|
||||
var (
|
||||
Ciphersuite_name = map[int32]string{
|
||||
0: "AES_128_GCM_SHA256",
|
||||
1: "AES_256_GCM_SHA384",
|
||||
2: "CHACHA20_POLY1305_SHA256",
|
||||
}
|
||||
Ciphersuite_value = map[string]int32{
|
||||
"AES_128_GCM_SHA256": 0,
|
||||
"AES_256_GCM_SHA384": 1,
|
||||
"CHACHA20_POLY1305_SHA256": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Ciphersuite) Enum() *Ciphersuite {
|
||||
p := new(Ciphersuite)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Ciphersuite) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Ciphersuite) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_common_common_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Ciphersuite) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_common_common_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Ciphersuite) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Ciphersuite.Descriptor instead.
|
||||
func (Ciphersuite) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_common_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// The TLS versions supported by S2A's handshaker module.
|
||||
type TLSVersion int32
|
||||
|
||||
const (
|
||||
TLSVersion_TLS1_2 TLSVersion = 0
|
||||
TLSVersion_TLS1_3 TLSVersion = 1
|
||||
)
|
||||
|
||||
// Enum value maps for TLSVersion.
|
||||
var (
|
||||
TLSVersion_name = map[int32]string{
|
||||
0: "TLS1_2",
|
||||
1: "TLS1_3",
|
||||
}
|
||||
TLSVersion_value = map[string]int32{
|
||||
"TLS1_2": 0,
|
||||
"TLS1_3": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x TLSVersion) Enum() *TLSVersion {
|
||||
p := new(TLSVersion)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x TLSVersion) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (TLSVersion) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_common_common_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (TLSVersion) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_common_common_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x TLSVersion) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TLSVersion.Descriptor instead.
|
||||
func (TLSVersion) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_common_common_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to IdentityOneof:
|
||||
//
|
||||
// *Identity_SpiffeId
|
||||
// *Identity_Hostname
|
||||
// *Identity_Uid
|
||||
// *Identity_Username
|
||||
// *Identity_GcpId
|
||||
IdentityOneof isIdentity_IdentityOneof `protobuf_oneof:"identity_oneof"`
|
||||
// Additional identity-specific attributes.
|
||||
Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *Identity) Reset() {
|
||||
*x = Identity{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_proto_common_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Identity) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Identity) ProtoMessage() {}
|
||||
|
||||
func (x *Identity) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_proto_common_common_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Identity.ProtoReflect.Descriptor instead.
|
||||
func (*Identity) Descriptor() ([]byte, []int) {
|
||||
return file_internal_proto_common_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *Identity) GetIdentityOneof() isIdentity_IdentityOneof {
|
||||
if m != nil {
|
||||
return m.IdentityOneof
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Identity) GetSpiffeId() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_SpiffeId); ok {
|
||||
return x.SpiffeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetHostname() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Hostname); ok {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetUid() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Uid); ok {
|
||||
return x.Uid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetUsername() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Username); ok {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetGcpId() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_GcpId); ok {
|
||||
return x.GcpId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetAttributes() map[string]string {
|
||||
if x != nil {
|
||||
return x.Attributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isIdentity_IdentityOneof interface {
|
||||
isIdentity_IdentityOneof()
|
||||
}
|
||||
|
||||
type Identity_SpiffeId struct {
|
||||
// The SPIFFE ID of a connection endpoint.
|
||||
SpiffeId string `protobuf:"bytes,1,opt,name=spiffe_id,json=spiffeId,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Hostname struct {
|
||||
// The hostname of a connection endpoint.
|
||||
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Uid struct {
|
||||
// The UID of a connection endpoint.
|
||||
Uid string `protobuf:"bytes,4,opt,name=uid,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Username struct {
|
||||
// The username of a connection endpoint.
|
||||
Username string `protobuf:"bytes,5,opt,name=username,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_GcpId struct {
|
||||
// The GCP ID of a connection endpoint.
|
||||
GcpId string `protobuf:"bytes,6,opt,name=gcp_id,json=gcpId,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Identity_SpiffeId) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Hostname) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Uid) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Username) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_GcpId) isIdentity_IdentityOneof() {}
|
||||
|
||||
var File_internal_proto_common_common_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_proto_common_common_proto_rawDesc = []byte{
|
||||
0x0a, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0xa8, 0x02, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x09,
|
||||
0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48,
|
||||
0x00, 0x52, 0x08, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x08, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
|
||||
0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x75, 0x69, 0x64,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x1c, 0x0a,
|
||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48,
|
||||
0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x06, 0x67,
|
||||
0x63, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x67,
|
||||
0x63, 0x70, 0x49, 0x64, 0x12, 0x43, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
|
||||
0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x74,
|
||||
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74,
|
||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x74, 0x79, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x2a, 0x5b, 0x0a, 0x0b, 0x43, 0x69,
|
||||
0x70, 0x68, 0x65, 0x72, 0x73, 0x75, 0x69, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x45, 0x53,
|
||||
0x5f, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10,
|
||||
0x00, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x47, 0x43, 0x4d,
|
||||
0x5f, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x48, 0x41,
|
||||
0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x5f, 0x53,
|
||||
0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x02, 0x2a, 0x24, 0x0a, 0x0a, 0x54, 0x4c, 0x53, 0x56, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x32, 0x10,
|
||||
0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x33, 0x10, 0x01, 0x42, 0x36, 0x5a,
|
||||
0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2f, 0x73, 0x32, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x5f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_proto_common_common_proto_rawDescOnce sync.Once
|
||||
file_internal_proto_common_common_proto_rawDescData = file_internal_proto_common_common_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_proto_common_common_proto_rawDescGZIP() []byte {
|
||||
file_internal_proto_common_common_proto_rawDescOnce.Do(func() {
|
||||
file_internal_proto_common_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_common_common_proto_rawDescData)
|
||||
})
|
||||
return file_internal_proto_common_common_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_proto_common_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_internal_proto_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_internal_proto_common_common_proto_goTypes = []any{
|
||||
(Ciphersuite)(0), // 0: s2a.proto.Ciphersuite
|
||||
(TLSVersion)(0), // 1: s2a.proto.TLSVersion
|
||||
(*Identity)(nil), // 2: s2a.proto.Identity
|
||||
nil, // 3: s2a.proto.Identity.AttributesEntry
|
||||
}
|
||||
var file_internal_proto_common_common_proto_depIdxs = []int32{
|
||||
3, // 0: s2a.proto.Identity.attributes:type_name -> s2a.proto.Identity.AttributesEntry
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_proto_common_common_proto_init() }
|
||||
func file_internal_proto_common_common_proto_init() {
|
||||
if File_internal_proto_common_common_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_proto_common_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Identity); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_internal_proto_common_common_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*Identity_SpiffeId)(nil),
|
||||
(*Identity_Hostname)(nil),
|
||||
(*Identity_Uid)(nil),
|
||||
(*Identity_Username)(nil),
|
||||
(*Identity_GcpId)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_proto_common_common_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_proto_common_common_proto_goTypes,
|
||||
DependencyIndexes: file_internal_proto_common_common_proto_depIdxs,
|
||||
EnumInfos: file_internal_proto_common_common_proto_enumTypes,
|
||||
MessageInfos: file_internal_proto_common_common_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_proto_common_common_proto = out.File
|
||||
file_internal_proto_common_common_proto_rawDesc = nil
|
||||
file_internal_proto_common_common_proto_goTypes = nil
|
||||
file_internal_proto_common_common_proto_depIdxs = nil
|
||||
}
|
||||
267
vendor/github.com/google/s2a-go/internal/proto/s2a_context_go_proto/s2a_context.pb.go
generated
vendored
Normal file
267
vendor/github.com/google/s2a-go/internal/proto/s2a_context_go_proto/s2a_context.pb.go
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.21.12
|
||||
// source: internal/proto/s2a_context/s2a_context.proto
|
||||
|
||||
package s2a_context_go_proto
|
||||
|
||||
import (
|
||||
common_go_proto "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type S2AContext struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The application protocol negotiated for this connection, e.g., 'grpc'.
|
||||
ApplicationProtocol string `protobuf:"bytes,1,opt,name=application_protocol,json=applicationProtocol,proto3" json:"application_protocol,omitempty"`
|
||||
// The TLS version number that the S2A's handshaker module used to set up the
|
||||
// session.
|
||||
TlsVersion common_go_proto.TLSVersion `protobuf:"varint,2,opt,name=tls_version,json=tlsVersion,proto3,enum=s2a.proto.TLSVersion" json:"tls_version,omitempty"`
|
||||
// The TLS ciphersuite negotiated by the S2A's handshaker module.
|
||||
Ciphersuite common_go_proto.Ciphersuite `protobuf:"varint,3,opt,name=ciphersuite,proto3,enum=s2a.proto.Ciphersuite" json:"ciphersuite,omitempty"`
|
||||
// The authenticated identity of the peer.
|
||||
PeerIdentity *common_go_proto.Identity `protobuf:"bytes,4,opt,name=peer_identity,json=peerIdentity,proto3" json:"peer_identity,omitempty"`
|
||||
// The local identity used during session setup. This could be:
|
||||
// - The local identity that the client specifies in ClientSessionStartReq.
|
||||
// - One of the local identities that the server specifies in
|
||||
// ServerSessionStartReq.
|
||||
// - If neither client or server specifies local identities, the S2A picks the
|
||||
// default one. In this case, this field will contain that identity.
|
||||
LocalIdentity *common_go_proto.Identity `protobuf:"bytes,5,opt,name=local_identity,json=localIdentity,proto3" json:"local_identity,omitempty"`
|
||||
// The SHA256 hash of the peer certificate used in the handshake.
|
||||
PeerCertFingerprint []byte `protobuf:"bytes,6,opt,name=peer_cert_fingerprint,json=peerCertFingerprint,proto3" json:"peer_cert_fingerprint,omitempty"`
|
||||
// The SHA256 hash of the local certificate used in the handshake.
|
||||
LocalCertFingerprint []byte `protobuf:"bytes,7,opt,name=local_cert_fingerprint,json=localCertFingerprint,proto3" json:"local_cert_fingerprint,omitempty"`
|
||||
// Set to true if a cached session was reused to resume the handshake.
|
||||
IsHandshakeResumed bool `protobuf:"varint,8,opt,name=is_handshake_resumed,json=isHandshakeResumed,proto3" json:"is_handshake_resumed,omitempty"`
|
||||
}
|
||||
|
||||
func (x *S2AContext) Reset() {
|
||||
*x = S2AContext{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_proto_s2a_context_s2a_context_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *S2AContext) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*S2AContext) ProtoMessage() {}
|
||||
|
||||
func (x *S2AContext) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_proto_s2a_context_s2a_context_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use S2AContext.ProtoReflect.Descriptor instead.
|
||||
func (*S2AContext) Descriptor() ([]byte, []int) {
|
||||
return file_internal_proto_s2a_context_s2a_context_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetApplicationProtocol() string {
|
||||
if x != nil {
|
||||
return x.ApplicationProtocol
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetTlsVersion() common_go_proto.TLSVersion {
|
||||
if x != nil {
|
||||
return x.TlsVersion
|
||||
}
|
||||
return common_go_proto.TLSVersion(0)
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetCiphersuite() common_go_proto.Ciphersuite {
|
||||
if x != nil {
|
||||
return x.Ciphersuite
|
||||
}
|
||||
return common_go_proto.Ciphersuite(0)
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetPeerIdentity() *common_go_proto.Identity {
|
||||
if x != nil {
|
||||
return x.PeerIdentity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLocalIdentity() *common_go_proto.Identity {
|
||||
if x != nil {
|
||||
return x.LocalIdentity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetPeerCertFingerprint() []byte {
|
||||
if x != nil {
|
||||
return x.PeerCertFingerprint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLocalCertFingerprint() []byte {
|
||||
if x != nil {
|
||||
return x.LocalCertFingerprint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetIsHandshakeResumed() bool {
|
||||
if x != nil {
|
||||
return x.IsHandshakeResumed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var File_internal_proto_s2a_context_s2a_context_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_proto_s2a_context_s2a_context_proto_rawDesc = []byte{
|
||||
0x0a, 0x2c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2f, 0x73, 0x32, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x73, 0x32, 0x61,
|
||||
0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09,
|
||||
0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x22, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x03,
|
||||
0x0a, 0x0a, 0x53, 0x32, 0x41, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x31, 0x0a, 0x14,
|
||||
0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x61, 0x70, 0x70, 0x6c,
|
||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12,
|
||||
0x36, 0x0a, 0x0b, 0x74, 0x6c, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x54, 0x4c, 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x74, 0x6c, 0x73,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x69, 0x70, 0x68, 0x65,
|
||||
0x72, 0x73, 0x75, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73,
|
||||
0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73,
|
||||
0x75, 0x69, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x75, 0x69, 0x74,
|
||||
0x65, 0x12, 0x38, 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0c, 0x70,
|
||||
0x65, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0e, 0x6c,
|
||||
0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49,
|
||||
0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x65, 0x65, 0x72, 0x5f,
|
||||
0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74,
|
||||
0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x16, 0x6c,
|
||||
0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e,
|
||||
0x74, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x73, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b,
|
||||
0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x12, 0x69, 0x73, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x75,
|
||||
0x6d, 0x65, 0x64, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x73, 0x32, 0x61, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x32, 0x61, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_proto_s2a_context_s2a_context_proto_rawDescOnce sync.Once
|
||||
file_internal_proto_s2a_context_s2a_context_proto_rawDescData = file_internal_proto_s2a_context_s2a_context_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_proto_s2a_context_s2a_context_proto_rawDescGZIP() []byte {
|
||||
file_internal_proto_s2a_context_s2a_context_proto_rawDescOnce.Do(func() {
|
||||
file_internal_proto_s2a_context_s2a_context_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_s2a_context_s2a_context_proto_rawDescData)
|
||||
})
|
||||
return file_internal_proto_s2a_context_s2a_context_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_proto_s2a_context_s2a_context_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_internal_proto_s2a_context_s2a_context_proto_goTypes = []any{
|
||||
(*S2AContext)(nil), // 0: s2a.proto.S2AContext
|
||||
(common_go_proto.TLSVersion)(0), // 1: s2a.proto.TLSVersion
|
||||
(common_go_proto.Ciphersuite)(0), // 2: s2a.proto.Ciphersuite
|
||||
(*common_go_proto.Identity)(nil), // 3: s2a.proto.Identity
|
||||
}
|
||||
var file_internal_proto_s2a_context_s2a_context_proto_depIdxs = []int32{
|
||||
1, // 0: s2a.proto.S2AContext.tls_version:type_name -> s2a.proto.TLSVersion
|
||||
2, // 1: s2a.proto.S2AContext.ciphersuite:type_name -> s2a.proto.Ciphersuite
|
||||
3, // 2: s2a.proto.S2AContext.peer_identity:type_name -> s2a.proto.Identity
|
||||
3, // 3: s2a.proto.S2AContext.local_identity:type_name -> s2a.proto.Identity
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_proto_s2a_context_s2a_context_proto_init() }
|
||||
func file_internal_proto_s2a_context_s2a_context_proto_init() {
|
||||
if File_internal_proto_s2a_context_s2a_context_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_proto_s2a_context_s2a_context_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*S2AContext); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_proto_s2a_context_s2a_context_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_proto_s2a_context_s2a_context_proto_goTypes,
|
||||
DependencyIndexes: file_internal_proto_s2a_context_s2a_context_proto_depIdxs,
|
||||
MessageInfos: file_internal_proto_s2a_context_s2a_context_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_proto_s2a_context_s2a_context_proto = out.File
|
||||
file_internal_proto_s2a_context_s2a_context_proto_rawDesc = nil
|
||||
file_internal_proto_s2a_context_s2a_context_proto_goTypes = nil
|
||||
file_internal_proto_s2a_context_s2a_context_proto_depIdxs = nil
|
||||
}
|
||||
1377
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a.pb.go
generated
vendored
Normal file
1377
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
174
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a_grpc.pb.go
generated
vendored
Normal file
174
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a_grpc.pb.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v3.21.12
|
||||
// source: internal/proto/s2a/s2a.proto
|
||||
|
||||
package s2a_go_proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
S2AService_SetUpSession_FullMethodName = "/s2a.proto.S2AService/SetUpSession"
|
||||
)
|
||||
|
||||
// S2AServiceClient is the client API for S2AService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type S2AServiceClient interface {
|
||||
// S2A service accepts a stream of session setup requests and returns a stream
|
||||
// of session setup responses. The client of this service is expected to send
|
||||
// exactly one client_start or server_start message followed by at least one
|
||||
// next message. Applications running TLS clients can send requests with
|
||||
// resumption_ticket messages only after the session is successfully set up.
|
||||
//
|
||||
// Every time S2A client sends a request, this service sends a response.
|
||||
// However, clients do not have to wait for service response before sending
|
||||
// the next request.
|
||||
SetUpSession(ctx context.Context, opts ...grpc.CallOption) (S2AService_SetUpSessionClient, error)
|
||||
}
|
||||
|
||||
type s2AServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewS2AServiceClient(cc grpc.ClientConnInterface) S2AServiceClient {
|
||||
return &s2AServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *s2AServiceClient) SetUpSession(ctx context.Context, opts ...grpc.CallOption) (S2AService_SetUpSessionClient, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &S2AService_ServiceDesc.Streams[0], S2AService_SetUpSession_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &s2AServiceSetUpSessionClient{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type S2AService_SetUpSessionClient interface {
|
||||
Send(*SessionReq) error
|
||||
Recv() (*SessionResp, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type s2AServiceSetUpSessionClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionClient) Send(m *SessionReq) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionClient) Recv() (*SessionResp, error) {
|
||||
m := new(SessionResp)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// S2AServiceServer is the server API for S2AService service.
|
||||
// All implementations must embed UnimplementedS2AServiceServer
|
||||
// for forward compatibility
|
||||
type S2AServiceServer interface {
|
||||
// S2A service accepts a stream of session setup requests and returns a stream
|
||||
// of session setup responses. The client of this service is expected to send
|
||||
// exactly one client_start or server_start message followed by at least one
|
||||
// next message. Applications running TLS clients can send requests with
|
||||
// resumption_ticket messages only after the session is successfully set up.
|
||||
//
|
||||
// Every time S2A client sends a request, this service sends a response.
|
||||
// However, clients do not have to wait for service response before sending
|
||||
// the next request.
|
||||
SetUpSession(S2AService_SetUpSessionServer) error
|
||||
mustEmbedUnimplementedS2AServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedS2AServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedS2AServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedS2AServiceServer) SetUpSession(S2AService_SetUpSessionServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SetUpSession not implemented")
|
||||
}
|
||||
func (UnimplementedS2AServiceServer) mustEmbedUnimplementedS2AServiceServer() {}
|
||||
|
||||
// UnsafeS2AServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to S2AServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeS2AServiceServer interface {
|
||||
mustEmbedUnimplementedS2AServiceServer()
|
||||
}
|
||||
|
||||
func RegisterS2AServiceServer(s grpc.ServiceRegistrar, srv S2AServiceServer) {
|
||||
s.RegisterService(&S2AService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _S2AService_SetUpSession_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(S2AServiceServer).SetUpSession(&s2AServiceSetUpSessionServer{ServerStream: stream})
|
||||
}
|
||||
|
||||
type S2AService_SetUpSessionServer interface {
|
||||
Send(*SessionResp) error
|
||||
Recv() (*SessionReq, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type s2AServiceSetUpSessionServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionServer) Send(m *SessionResp) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionServer) Recv() (*SessionReq, error) {
|
||||
m := new(SessionReq)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// S2AService_ServiceDesc is the grpc.ServiceDesc for S2AService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var S2AService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "s2a.proto.S2AService",
|
||||
HandlerType: (*S2AServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SetUpSession",
|
||||
Handler: _S2AService_SetUpSession_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "internal/proto/s2a/s2a.proto",
|
||||
}
|
||||
549
vendor/github.com/google/s2a-go/internal/proto/v2/common_go_proto/common.pb.go
generated
vendored
Normal file
549
vendor/github.com/google/s2a-go/internal/proto/v2/common_go_proto/common.pb.go
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.21.12
|
||||
// source: internal/proto/v2/common/common.proto
|
||||
|
||||
package common_go_proto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// The TLS 1.0-1.2 ciphersuites that the application can negotiate when using
|
||||
// S2A.
|
||||
type Ciphersuite int32
|
||||
|
||||
const (
|
||||
Ciphersuite_CIPHERSUITE_UNSPECIFIED Ciphersuite = 0
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 Ciphersuite = 1
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 Ciphersuite = 2
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 Ciphersuite = 3
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256 Ciphersuite = 4
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384 Ciphersuite = 5
|
||||
Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 Ciphersuite = 6
|
||||
)
|
||||
|
||||
// Enum value maps for Ciphersuite.
|
||||
var (
|
||||
Ciphersuite_name = map[int32]string{
|
||||
0: "CIPHERSUITE_UNSPECIFIED",
|
||||
1: "CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
2: "CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
3: "CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
4: "CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
5: "CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
6: "CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
}
|
||||
Ciphersuite_value = map[string]int32{
|
||||
"CIPHERSUITE_UNSPECIFIED": 0,
|
||||
"CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": 1,
|
||||
"CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": 2,
|
||||
"CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": 3,
|
||||
"CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256": 4,
|
||||
"CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384": 5,
|
||||
"CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": 6,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Ciphersuite) Enum() *Ciphersuite {
|
||||
p := new(Ciphersuite)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Ciphersuite) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Ciphersuite) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_v2_common_common_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Ciphersuite) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_v2_common_common_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Ciphersuite) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Ciphersuite.Descriptor instead.
|
||||
func (Ciphersuite) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_common_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// The TLS versions supported by S2A's handshaker module.
|
||||
type TLSVersion int32
|
||||
|
||||
const (
|
||||
TLSVersion_TLS_VERSION_UNSPECIFIED TLSVersion = 0
|
||||
TLSVersion_TLS_VERSION_1_0 TLSVersion = 1
|
||||
TLSVersion_TLS_VERSION_1_1 TLSVersion = 2
|
||||
TLSVersion_TLS_VERSION_1_2 TLSVersion = 3
|
||||
TLSVersion_TLS_VERSION_1_3 TLSVersion = 4
|
||||
)
|
||||
|
||||
// Enum value maps for TLSVersion.
|
||||
var (
|
||||
TLSVersion_name = map[int32]string{
|
||||
0: "TLS_VERSION_UNSPECIFIED",
|
||||
1: "TLS_VERSION_1_0",
|
||||
2: "TLS_VERSION_1_1",
|
||||
3: "TLS_VERSION_1_2",
|
||||
4: "TLS_VERSION_1_3",
|
||||
}
|
||||
TLSVersion_value = map[string]int32{
|
||||
"TLS_VERSION_UNSPECIFIED": 0,
|
||||
"TLS_VERSION_1_0": 1,
|
||||
"TLS_VERSION_1_1": 2,
|
||||
"TLS_VERSION_1_2": 3,
|
||||
"TLS_VERSION_1_3": 4,
|
||||
}
|
||||
)
|
||||
|
||||
func (x TLSVersion) Enum() *TLSVersion {
|
||||
p := new(TLSVersion)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x TLSVersion) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (TLSVersion) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_v2_common_common_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (TLSVersion) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_v2_common_common_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x TLSVersion) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TLSVersion.Descriptor instead.
|
||||
func (TLSVersion) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_common_common_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
// The side in the TLS connection.
|
||||
type ConnectionSide int32
|
||||
|
||||
const (
|
||||
ConnectionSide_CONNECTION_SIDE_UNSPECIFIED ConnectionSide = 0
|
||||
ConnectionSide_CONNECTION_SIDE_CLIENT ConnectionSide = 1
|
||||
ConnectionSide_CONNECTION_SIDE_SERVER ConnectionSide = 2
|
||||
)
|
||||
|
||||
// Enum value maps for ConnectionSide.
|
||||
var (
|
||||
ConnectionSide_name = map[int32]string{
|
||||
0: "CONNECTION_SIDE_UNSPECIFIED",
|
||||
1: "CONNECTION_SIDE_CLIENT",
|
||||
2: "CONNECTION_SIDE_SERVER",
|
||||
}
|
||||
ConnectionSide_value = map[string]int32{
|
||||
"CONNECTION_SIDE_UNSPECIFIED": 0,
|
||||
"CONNECTION_SIDE_CLIENT": 1,
|
||||
"CONNECTION_SIDE_SERVER": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x ConnectionSide) Enum() *ConnectionSide {
|
||||
p := new(ConnectionSide)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ConnectionSide) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (ConnectionSide) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_v2_common_common_proto_enumTypes[2].Descriptor()
|
||||
}
|
||||
|
||||
func (ConnectionSide) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_v2_common_common_proto_enumTypes[2]
|
||||
}
|
||||
|
||||
func (x ConnectionSide) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionSide.Descriptor instead.
|
||||
func (ConnectionSide) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_common_common_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
// The ALPN protocols that the application can negotiate during a TLS handshake.
|
||||
type AlpnProtocol int32
|
||||
|
||||
const (
|
||||
AlpnProtocol_ALPN_PROTOCOL_UNSPECIFIED AlpnProtocol = 0
|
||||
AlpnProtocol_ALPN_PROTOCOL_GRPC AlpnProtocol = 1
|
||||
AlpnProtocol_ALPN_PROTOCOL_HTTP2 AlpnProtocol = 2
|
||||
AlpnProtocol_ALPN_PROTOCOL_HTTP1_1 AlpnProtocol = 3
|
||||
)
|
||||
|
||||
// Enum value maps for AlpnProtocol.
|
||||
var (
|
||||
AlpnProtocol_name = map[int32]string{
|
||||
0: "ALPN_PROTOCOL_UNSPECIFIED",
|
||||
1: "ALPN_PROTOCOL_GRPC",
|
||||
2: "ALPN_PROTOCOL_HTTP2",
|
||||
3: "ALPN_PROTOCOL_HTTP1_1",
|
||||
}
|
||||
AlpnProtocol_value = map[string]int32{
|
||||
"ALPN_PROTOCOL_UNSPECIFIED": 0,
|
||||
"ALPN_PROTOCOL_GRPC": 1,
|
||||
"ALPN_PROTOCOL_HTTP2": 2,
|
||||
"ALPN_PROTOCOL_HTTP1_1": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x AlpnProtocol) Enum() *AlpnProtocol {
|
||||
p := new(AlpnProtocol)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x AlpnProtocol) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (AlpnProtocol) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_proto_v2_common_common_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (AlpnProtocol) Type() protoreflect.EnumType {
|
||||
return &file_internal_proto_v2_common_common_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x AlpnProtocol) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AlpnProtocol.Descriptor instead.
|
||||
func (AlpnProtocol) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_common_common_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to IdentityOneof:
|
||||
//
|
||||
// *Identity_SpiffeId
|
||||
// *Identity_Hostname
|
||||
// *Identity_Uid
|
||||
// *Identity_Username
|
||||
// *Identity_GcpId
|
||||
IdentityOneof isIdentity_IdentityOneof `protobuf_oneof:"identity_oneof"`
|
||||
// Additional identity-specific attributes.
|
||||
Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *Identity) Reset() {
|
||||
*x = Identity{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_proto_v2_common_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Identity) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Identity) ProtoMessage() {}
|
||||
|
||||
func (x *Identity) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_proto_v2_common_common_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Identity.ProtoReflect.Descriptor instead.
|
||||
func (*Identity) Descriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_common_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *Identity) GetIdentityOneof() isIdentity_IdentityOneof {
|
||||
if m != nil {
|
||||
return m.IdentityOneof
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Identity) GetSpiffeId() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_SpiffeId); ok {
|
||||
return x.SpiffeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetHostname() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Hostname); ok {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetUid() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Uid); ok {
|
||||
return x.Uid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetUsername() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_Username); ok {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetGcpId() string {
|
||||
if x, ok := x.GetIdentityOneof().(*Identity_GcpId); ok {
|
||||
return x.GcpId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Identity) GetAttributes() map[string]string {
|
||||
if x != nil {
|
||||
return x.Attributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isIdentity_IdentityOneof interface {
|
||||
isIdentity_IdentityOneof()
|
||||
}
|
||||
|
||||
type Identity_SpiffeId struct {
|
||||
// The SPIFFE ID of a connection endpoint.
|
||||
SpiffeId string `protobuf:"bytes,1,opt,name=spiffe_id,json=spiffeId,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Hostname struct {
|
||||
// The hostname of a connection endpoint.
|
||||
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Uid struct {
|
||||
// The UID of a connection endpoint.
|
||||
Uid string `protobuf:"bytes,4,opt,name=uid,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_Username struct {
|
||||
// The username of a connection endpoint.
|
||||
Username string `protobuf:"bytes,5,opt,name=username,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Identity_GcpId struct {
|
||||
// The GCP ID of a connection endpoint.
|
||||
GcpId string `protobuf:"bytes,6,opt,name=gcp_id,json=gcpId,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Identity_SpiffeId) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Hostname) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Uid) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_Username) isIdentity_IdentityOneof() {}
|
||||
|
||||
func (*Identity_GcpId) isIdentity_IdentityOneof() {}
|
||||
|
||||
var File_internal_proto_v2_common_common_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_proto_v2_common_common_proto_rawDesc = []byte{
|
||||
0x0a, 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
|
||||
0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2e, 0x76, 0x32, 0x22, 0xab, 0x02, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x74, 0x79, 0x12, 0x1d, 0x0a, 0x09, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x49,
|
||||
0x64, 0x12, 0x1c, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x12, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03,
|
||||
0x75, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x17, 0x0a, 0x06, 0x67, 0x63, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x48, 0x00, 0x52, 0x05, 0x67, 0x63, 0x70, 0x49, 0x64, 0x12, 0x46, 0x0a, 0x0a, 0x61, 0x74,
|
||||
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26,
|
||||
0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x32, 0x2e, 0x49, 0x64,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
|
||||
0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
||||
0x01, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6f, 0x6e,
|
||||
0x65, 0x6f, 0x66, 0x2a, 0xee, 0x02, 0x0a, 0x0b, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x75,
|
||||
0x69, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x53, 0x55, 0x49,
|
||||
0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
||||
0x12, 0x33, 0x0a, 0x2f, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x53, 0x55, 0x49, 0x54, 0x45, 0x5f,
|
||||
0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48,
|
||||
0x5f, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x32, 0x35, 0x36, 0x10, 0x01, 0x12, 0x33, 0x0a, 0x2f, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x53,
|
||||
0x55, 0x49, 0x54, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x53, 0x41,
|
||||
0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x47, 0x43,
|
||||
0x4d, 0x5f, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x02, 0x12, 0x39, 0x0a, 0x35, 0x43, 0x49,
|
||||
0x50, 0x48, 0x45, 0x52, 0x53, 0x55, 0x49, 0x54, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f,
|
||||
0x45, 0x43, 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x41, 0x43, 0x48,
|
||||
0x41, 0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x32, 0x35, 0x36, 0x10, 0x03, 0x12, 0x31, 0x0a, 0x2d, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x53,
|
||||
0x55, 0x49, 0x54, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x52, 0x53, 0x41, 0x5f, 0x57,
|
||||
0x49, 0x54, 0x48, 0x5f, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x04, 0x12, 0x31, 0x0a, 0x2d, 0x43, 0x49, 0x50, 0x48,
|
||||
0x45, 0x52, 0x53, 0x55, 0x49, 0x54, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x52, 0x53,
|
||||
0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x41, 0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x47,
|
||||
0x43, 0x4d, 0x5f, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x10, 0x05, 0x12, 0x37, 0x0a, 0x33, 0x43,
|
||||
0x49, 0x50, 0x48, 0x45, 0x52, 0x53, 0x55, 0x49, 0x54, 0x45, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x45,
|
||||
0x5f, 0x52, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41,
|
||||
0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x5f, 0x53, 0x48, 0x41, 0x32,
|
||||
0x35, 0x36, 0x10, 0x06, 0x2a, 0x7d, 0x0a, 0x0a, 0x54, 0x4c, 0x53, 0x56, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x4c, 0x53, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f,
|
||||
0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
|
||||
0x13, 0x0a, 0x0f, 0x54, 0x4c, 0x53, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x31,
|
||||
0x5f, 0x30, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x4c, 0x53, 0x5f, 0x56, 0x45, 0x52, 0x53,
|
||||
0x49, 0x4f, 0x4e, 0x5f, 0x31, 0x5f, 0x31, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x4c, 0x53,
|
||||
0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x31, 0x5f, 0x32, 0x10, 0x03, 0x12, 0x13,
|
||||
0x0a, 0x0f, 0x54, 0x4c, 0x53, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x31, 0x5f,
|
||||
0x33, 0x10, 0x04, 0x2a, 0x69, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x53, 0x69, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54,
|
||||
0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
|
||||
0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43,
|
||||
0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54,
|
||||
0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e,
|
||||
0x5f, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x79,
|
||||
0x0a, 0x0c, 0x41, 0x6c, 0x70, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1d,
|
||||
0x0a, 0x19, 0x41, 0x4c, 0x50, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f,
|
||||
0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a,
|
||||
0x12, 0x41, 0x4c, 0x50, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x47,
|
||||
0x52, 0x50, 0x43, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x4c, 0x50, 0x4e, 0x5f, 0x50, 0x52,
|
||||
0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x32, 0x10, 0x02, 0x12, 0x19,
|
||||
0x0a, 0x15, 0x41, 0x4c, 0x50, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f,
|
||||
0x48, 0x54, 0x54, 0x50, 0x31, 0x5f, 0x31, 0x10, 0x03, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x73,
|
||||
0x32, 0x61, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x5f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_proto_v2_common_common_proto_rawDescOnce sync.Once
|
||||
file_internal_proto_v2_common_common_proto_rawDescData = file_internal_proto_v2_common_common_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_proto_v2_common_common_proto_rawDescGZIP() []byte {
|
||||
file_internal_proto_v2_common_common_proto_rawDescOnce.Do(func() {
|
||||
file_internal_proto_v2_common_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_v2_common_common_proto_rawDescData)
|
||||
})
|
||||
return file_internal_proto_v2_common_common_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_proto_v2_common_common_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_internal_proto_v2_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_internal_proto_v2_common_common_proto_goTypes = []any{
|
||||
(Ciphersuite)(0), // 0: s2a.proto.v2.Ciphersuite
|
||||
(TLSVersion)(0), // 1: s2a.proto.v2.TLSVersion
|
||||
(ConnectionSide)(0), // 2: s2a.proto.v2.ConnectionSide
|
||||
(AlpnProtocol)(0), // 3: s2a.proto.v2.AlpnProtocol
|
||||
(*Identity)(nil), // 4: s2a.proto.v2.Identity
|
||||
nil, // 5: s2a.proto.v2.Identity.AttributesEntry
|
||||
}
|
||||
var file_internal_proto_v2_common_common_proto_depIdxs = []int32{
|
||||
5, // 0: s2a.proto.v2.Identity.attributes:type_name -> s2a.proto.v2.Identity.AttributesEntry
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_proto_v2_common_common_proto_init() }
|
||||
func file_internal_proto_v2_common_common_proto_init() {
|
||||
if File_internal_proto_v2_common_common_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_proto_v2_common_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Identity); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_internal_proto_v2_common_common_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*Identity_SpiffeId)(nil),
|
||||
(*Identity_Hostname)(nil),
|
||||
(*Identity_Uid)(nil),
|
||||
(*Identity_Username)(nil),
|
||||
(*Identity_GcpId)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_proto_v2_common_common_proto_rawDesc,
|
||||
NumEnums: 4,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_proto_v2_common_common_proto_goTypes,
|
||||
DependencyIndexes: file_internal_proto_v2_common_common_proto_depIdxs,
|
||||
EnumInfos: file_internal_proto_v2_common_common_proto_enumTypes,
|
||||
MessageInfos: file_internal_proto_v2_common_common_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_proto_v2_common_common_proto = out.File
|
||||
file_internal_proto_v2_common_common_proto_rawDesc = nil
|
||||
file_internal_proto_v2_common_common_proto_goTypes = nil
|
||||
file_internal_proto_v2_common_common_proto_depIdxs = nil
|
||||
}
|
||||
249
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_context_go_proto/s2a_context.pb.go
generated
vendored
Normal file
249
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_context_go_proto/s2a_context.pb.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v3.21.12
|
||||
// source: internal/proto/v2/s2a_context/s2a_context.proto
|
||||
|
||||
package s2a_context_go_proto
|
||||
|
||||
import (
|
||||
common_go_proto "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type S2AContext struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The SPIFFE ID from the peer leaf certificate, if present.
|
||||
//
|
||||
// This field is only populated if the leaf certificate is a valid SPIFFE
|
||||
// SVID; in particular, there is a unique URI SAN and this URI SAN is a valid
|
||||
// SPIFFE ID.
|
||||
LeafCertSpiffeId string `protobuf:"bytes,1,opt,name=leaf_cert_spiffe_id,json=leafCertSpiffeId,proto3" json:"leaf_cert_spiffe_id,omitempty"`
|
||||
// The URIs that are present in the SubjectAltName extension of the peer leaf
|
||||
// certificate.
|
||||
//
|
||||
// Note that the extracted URIs are not validated and may not be properly
|
||||
// formatted.
|
||||
LeafCertUris []string `protobuf:"bytes,2,rep,name=leaf_cert_uris,json=leafCertUris,proto3" json:"leaf_cert_uris,omitempty"`
|
||||
// The DNSNames that are present in the SubjectAltName extension of the peer
|
||||
// leaf certificate.
|
||||
LeafCertDnsnames []string `protobuf:"bytes,3,rep,name=leaf_cert_dnsnames,json=leafCertDnsnames,proto3" json:"leaf_cert_dnsnames,omitempty"`
|
||||
// The (ordered) list of fingerprints in the certificate chain used to verify
|
||||
// the given leaf certificate. The order MUST be from leaf certificate
|
||||
// fingerprint to root certificate fingerprint.
|
||||
//
|
||||
// A fingerprint is the base-64 encoding of the SHA256 hash of the
|
||||
// DER-encoding of a certificate. The list MAY be populated even if the peer
|
||||
// certificate chain was NOT validated successfully.
|
||||
PeerCertificateChainFingerprints []string `protobuf:"bytes,4,rep,name=peer_certificate_chain_fingerprints,json=peerCertificateChainFingerprints,proto3" json:"peer_certificate_chain_fingerprints,omitempty"`
|
||||
// The local identity used during session setup.
|
||||
LocalIdentity *common_go_proto.Identity `protobuf:"bytes,9,opt,name=local_identity,json=localIdentity,proto3" json:"local_identity,omitempty"`
|
||||
// The SHA256 hash of the DER-encoding of the local leaf certificate used in
|
||||
// the handshake.
|
||||
LocalLeafCertFingerprint []byte `protobuf:"bytes,6,opt,name=local_leaf_cert_fingerprint,json=localLeafCertFingerprint,proto3" json:"local_leaf_cert_fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
func (x *S2AContext) Reset() {
|
||||
*x = S2AContext{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_proto_v2_s2a_context_s2a_context_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *S2AContext) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*S2AContext) ProtoMessage() {}
|
||||
|
||||
func (x *S2AContext) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_proto_v2_s2a_context_s2a_context_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use S2AContext.ProtoReflect.Descriptor instead.
|
||||
func (*S2AContext) Descriptor() ([]byte, []int) {
|
||||
return file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLeafCertSpiffeId() string {
|
||||
if x != nil {
|
||||
return x.LeafCertSpiffeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLeafCertUris() []string {
|
||||
if x != nil {
|
||||
return x.LeafCertUris
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLeafCertDnsnames() []string {
|
||||
if x != nil {
|
||||
return x.LeafCertDnsnames
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetPeerCertificateChainFingerprints() []string {
|
||||
if x != nil {
|
||||
return x.PeerCertificateChainFingerprints
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLocalIdentity() *common_go_proto.Identity {
|
||||
if x != nil {
|
||||
return x.LocalIdentity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *S2AContext) GetLocalLeafCertFingerprint() []byte {
|
||||
if x != nil {
|
||||
return x.LocalLeafCertFingerprint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_internal_proto_v2_s2a_context_s2a_context_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_proto_v2_s2a_context_s2a_context_proto_rawDesc = []byte{
|
||||
0x0a, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2f, 0x76, 0x32, 0x2f, 0x73, 0x32, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2f,
|
||||
0x73, 0x32, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x0c, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x32, 0x1a,
|
||||
0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||
0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xee, 0x02, 0x0a, 0x0a, 0x53, 0x32, 0x41, 0x43, 0x6f,
|
||||
0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x63, 0x65,
|
||||
0x72, 0x74, 0x5f, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x10, 0x6c, 0x65, 0x61, 0x66, 0x43, 0x65, 0x72, 0x74, 0x53, 0x70, 0x69, 0x66,
|
||||
0x66, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x63, 0x65, 0x72,
|
||||
0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x65,
|
||||
0x61, 0x66, 0x43, 0x65, 0x72, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x65,
|
||||
0x61, 0x66, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x64, 0x6e, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x73,
|
||||
0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x65, 0x61, 0x66, 0x43, 0x65, 0x72, 0x74,
|
||||
0x44, 0x6e, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x23, 0x70, 0x65, 0x65, 0x72,
|
||||
0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x20, 0x70, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x67, 0x65,
|
||||
0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
|
||||
0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x16, 0x2e, 0x73, 0x32, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x32, 0x2e, 0x49,
|
||||
0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x1b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
|
||||
0x6c, 0x65, 0x61, 0x66, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x6c, 0x4c, 0x65, 0x61, 0x66, 0x43, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72,
|
||||
0x70, 0x72, 0x69, 0x6e, 0x74, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10,
|
||||
0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x73, 0x32, 0x61,
|
||||
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||
0x76, 0x32, 0x2f, 0x73, 0x32, 0x61, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x67,
|
||||
0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescOnce sync.Once
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescData = file_internal_proto_v2_s2a_context_s2a_context_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescGZIP() []byte {
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescOnce.Do(func() {
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescData)
|
||||
})
|
||||
return file_internal_proto_v2_s2a_context_s2a_context_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_proto_v2_s2a_context_s2a_context_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_internal_proto_v2_s2a_context_s2a_context_proto_goTypes = []any{
|
||||
(*S2AContext)(nil), // 0: s2a.proto.v2.S2AContext
|
||||
(*common_go_proto.Identity)(nil), // 1: s2a.proto.v2.Identity
|
||||
}
|
||||
var file_internal_proto_v2_s2a_context_s2a_context_proto_depIdxs = []int32{
|
||||
1, // 0: s2a.proto.v2.S2AContext.local_identity:type_name -> s2a.proto.v2.Identity
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_proto_v2_s2a_context_s2a_context_proto_init() }
|
||||
func file_internal_proto_v2_s2a_context_s2a_context_proto_init() {
|
||||
if File_internal_proto_v2_s2a_context_s2a_context_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*S2AContext); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_proto_v2_s2a_context_s2a_context_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_proto_v2_s2a_context_s2a_context_proto_goTypes,
|
||||
DependencyIndexes: file_internal_proto_v2_s2a_context_s2a_context_proto_depIdxs,
|
||||
MessageInfos: file_internal_proto_v2_s2a_context_s2a_context_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_proto_v2_s2a_context_s2a_context_proto = out.File
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_rawDesc = nil
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_goTypes = nil
|
||||
file_internal_proto_v2_s2a_context_s2a_context_proto_depIdxs = nil
|
||||
}
|
||||
2518
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a.pb.go
generated
vendored
Normal file
2518
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
160
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a_grpc.pb.go
generated
vendored
Normal file
160
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a_grpc.pb.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v3.21.12
|
||||
// source: internal/proto/v2/s2a/s2a.proto
|
||||
|
||||
package s2a_go_proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
S2AService_SetUpSession_FullMethodName = "/s2a.proto.v2.S2AService/SetUpSession"
|
||||
)
|
||||
|
||||
// S2AServiceClient is the client API for S2AService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type S2AServiceClient interface {
|
||||
// SetUpSession is a bidirectional stream used by applications to offload
|
||||
// operations from the TLS handshake.
|
||||
SetUpSession(ctx context.Context, opts ...grpc.CallOption) (S2AService_SetUpSessionClient, error)
|
||||
}
|
||||
|
||||
type s2AServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewS2AServiceClient(cc grpc.ClientConnInterface) S2AServiceClient {
|
||||
return &s2AServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *s2AServiceClient) SetUpSession(ctx context.Context, opts ...grpc.CallOption) (S2AService_SetUpSessionClient, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &S2AService_ServiceDesc.Streams[0], S2AService_SetUpSession_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &s2AServiceSetUpSessionClient{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type S2AService_SetUpSessionClient interface {
|
||||
Send(*SessionReq) error
|
||||
Recv() (*SessionResp, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type s2AServiceSetUpSessionClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionClient) Send(m *SessionReq) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionClient) Recv() (*SessionResp, error) {
|
||||
m := new(SessionResp)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// S2AServiceServer is the server API for S2AService service.
|
||||
// All implementations must embed UnimplementedS2AServiceServer
|
||||
// for forward compatibility
|
||||
type S2AServiceServer interface {
|
||||
// SetUpSession is a bidirectional stream used by applications to offload
|
||||
// operations from the TLS handshake.
|
||||
SetUpSession(S2AService_SetUpSessionServer) error
|
||||
mustEmbedUnimplementedS2AServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedS2AServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedS2AServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedS2AServiceServer) SetUpSession(S2AService_SetUpSessionServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SetUpSession not implemented")
|
||||
}
|
||||
func (UnimplementedS2AServiceServer) mustEmbedUnimplementedS2AServiceServer() {}
|
||||
|
||||
// UnsafeS2AServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to S2AServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeS2AServiceServer interface {
|
||||
mustEmbedUnimplementedS2AServiceServer()
|
||||
}
|
||||
|
||||
func RegisterS2AServiceServer(s grpc.ServiceRegistrar, srv S2AServiceServer) {
|
||||
s.RegisterService(&S2AService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _S2AService_SetUpSession_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(S2AServiceServer).SetUpSession(&s2AServiceSetUpSessionServer{ServerStream: stream})
|
||||
}
|
||||
|
||||
type S2AService_SetUpSessionServer interface {
|
||||
Send(*SessionResp) error
|
||||
Recv() (*SessionReq, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type s2AServiceSetUpSessionServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionServer) Send(m *SessionResp) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *s2AServiceSetUpSessionServer) Recv() (*SessionReq, error) {
|
||||
m := new(SessionReq)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// S2AService_ServiceDesc is the grpc.ServiceDesc for S2AService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var S2AService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "s2a.proto.v2.S2AService",
|
||||
HandlerType: (*S2AServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SetUpSession",
|
||||
Handler: _S2AService_SetUpSession_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "internal/proto/v2/s2a/s2a.proto",
|
||||
}
|
||||
34
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aeadcrypter.go
generated
vendored
Normal file
34
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aeadcrypter.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package aeadcrypter provides the interface for AEAD cipher implementations
|
||||
// used by S2A's record protocol.
|
||||
package aeadcrypter
|
||||
|
||||
// S2AAEADCrypter is the interface for an AEAD cipher used by the S2A record
|
||||
// protocol.
|
||||
type S2AAEADCrypter interface {
|
||||
// Encrypt encrypts the plaintext and computes the tag of dst and plaintext.
|
||||
// dst and plaintext may fully overlap or not at all.
|
||||
Encrypt(dst, plaintext, nonce, aad []byte) ([]byte, error)
|
||||
// Decrypt decrypts ciphertext and verifies the tag. dst and ciphertext may
|
||||
// fully overlap or not at all.
|
||||
Decrypt(dst, ciphertext, nonce, aad []byte) ([]byte, error)
|
||||
// TagSize returns the tag size in bytes.
|
||||
TagSize() int
|
||||
}
|
||||
70
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aesgcm.go
generated
vendored
Normal file
70
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aesgcm.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package aeadcrypter
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Supported key sizes in bytes.
|
||||
const (
|
||||
AES128GCMKeySize = 16
|
||||
AES256GCMKeySize = 32
|
||||
)
|
||||
|
||||
// aesgcm is the struct that holds an AES-GCM cipher for the S2A AEAD crypter.
|
||||
type aesgcm struct {
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
// NewAESGCM creates an AES-GCM crypter instance. Note that the key must be
|
||||
// either 128 bits or 256 bits.
|
||||
func NewAESGCM(key []byte) (S2AAEADCrypter, error) {
|
||||
if len(key) != AES128GCMKeySize && len(key) != AES256GCMKeySize {
|
||||
return nil, fmt.Errorf("%d or %d bytes, given: %d", AES128GCMKeySize, AES256GCMKeySize, len(key))
|
||||
}
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &aesgcm{aead: a}, nil
|
||||
}
|
||||
|
||||
// Encrypt is the encryption function. dst can contain bytes at the beginning of
|
||||
// the ciphertext that will not be encrypted but will be authenticated. If dst
|
||||
// has enough capacity to hold these bytes, the ciphertext and the tag, no
|
||||
// allocation and copy operations will be performed. dst and plaintext may
|
||||
// fully overlap or not at all.
|
||||
func (s *aesgcm) Encrypt(dst, plaintext, nonce, aad []byte) ([]byte, error) {
|
||||
return encrypt(s.aead, dst, plaintext, nonce, aad)
|
||||
}
|
||||
|
||||
func (s *aesgcm) Decrypt(dst, ciphertext, nonce, aad []byte) ([]byte, error) {
|
||||
return decrypt(s.aead, dst, ciphertext, nonce, aad)
|
||||
}
|
||||
|
||||
func (s *aesgcm) TagSize() int {
|
||||
return TagSize
|
||||
}
|
||||
67
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/chachapoly.go
generated
vendored
Normal file
67
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/chachapoly.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package aeadcrypter
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// Supported key size in bytes.
|
||||
const (
|
||||
Chacha20Poly1305KeySize = 32
|
||||
)
|
||||
|
||||
// chachapoly is the struct that holds a CHACHA-POLY cipher for the S2A AEAD
|
||||
// crypter.
|
||||
type chachapoly struct {
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
// NewChachaPoly creates a Chacha-Poly crypter instance. Note that the key must
|
||||
// be Chacha20Poly1305KeySize bytes in length.
|
||||
func NewChachaPoly(key []byte) (S2AAEADCrypter, error) {
|
||||
if len(key) != Chacha20Poly1305KeySize {
|
||||
return nil, fmt.Errorf("%d bytes, given: %d", Chacha20Poly1305KeySize, len(key))
|
||||
}
|
||||
c, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chachapoly{aead: c}, nil
|
||||
}
|
||||
|
||||
// Encrypt is the encryption function. dst can contain bytes at the beginning of
|
||||
// the ciphertext that will not be encrypted but will be authenticated. If dst
|
||||
// has enough capacity to hold these bytes, the ciphertext and the tag, no
|
||||
// allocation and copy operations will be performed. dst and plaintext may
|
||||
// fully overlap or not at all.
|
||||
func (s *chachapoly) Encrypt(dst, plaintext, nonce, aad []byte) ([]byte, error) {
|
||||
return encrypt(s.aead, dst, plaintext, nonce, aad)
|
||||
}
|
||||
|
||||
func (s *chachapoly) Decrypt(dst, ciphertext, nonce, aad []byte) ([]byte, error) {
|
||||
return decrypt(s.aead, dst, ciphertext, nonce, aad)
|
||||
}
|
||||
|
||||
func (s *chachapoly) TagSize() int {
|
||||
return TagSize
|
||||
}
|
||||
92
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/common.go
generated
vendored
Normal file
92
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/common.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package aeadcrypter
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// TagSize is the tag size in bytes for AES-128-GCM-SHA256,
|
||||
// AES-256-GCM-SHA384, and CHACHA20-POLY1305-SHA256.
|
||||
TagSize = 16
|
||||
// NonceSize is the size of the nonce in number of bytes for
|
||||
// AES-128-GCM-SHA256, AES-256-GCM-SHA384, and CHACHA20-POLY1305-SHA256.
|
||||
NonceSize = 12
|
||||
// SHA256DigestSize is the digest size of sha256 in bytes.
|
||||
SHA256DigestSize = 32
|
||||
// SHA384DigestSize is the digest size of sha384 in bytes.
|
||||
SHA384DigestSize = 48
|
||||
)
|
||||
|
||||
// sliceForAppend takes a slice and a requested number of bytes. It returns a
|
||||
// slice with the contents of the given slice followed by that many bytes and a
|
||||
// second slice that aliases into it and contains only the extra bytes. If the
|
||||
// original slice has sufficient capacity then no allocation is performed.
|
||||
func sliceForAppend(in []byte, n int) (head, tail []byte) {
|
||||
if total := len(in) + n; cap(in) >= total {
|
||||
head = in[:total]
|
||||
} else {
|
||||
head = make([]byte, total)
|
||||
copy(head, in)
|
||||
}
|
||||
tail = head[len(in):]
|
||||
return head, tail
|
||||
}
|
||||
|
||||
// encrypt is the encryption function for an AEAD crypter. aead determines
|
||||
// the type of AEAD crypter. dst can contain bytes at the beginning of the
|
||||
// ciphertext that will not be encrypted but will be authenticated. If dst has
|
||||
// enough capacity to hold these bytes, the ciphertext and the tag, no
|
||||
// allocation and copy operations will be performed. dst and plaintext may
|
||||
// fully overlap or not at all.
|
||||
func encrypt(aead cipher.AEAD, dst, plaintext, nonce, aad []byte) ([]byte, error) {
|
||||
if len(nonce) != NonceSize {
|
||||
return nil, fmt.Errorf("nonce size must be %d bytes. received: %d", NonceSize, len(nonce))
|
||||
}
|
||||
// If we need to allocate an output buffer, we want to include space for
|
||||
// the tag to avoid forcing the caller to reallocate as well.
|
||||
dlen := len(dst)
|
||||
dst, out := sliceForAppend(dst, len(plaintext)+TagSize)
|
||||
data := out[:len(plaintext)]
|
||||
copy(data, plaintext) // data may fully overlap plaintext
|
||||
|
||||
// Seal appends the ciphertext and the tag to its first argument and
|
||||
// returns the updated slice. However, sliceForAppend above ensures that
|
||||
// dst has enough capacity to avoid a reallocation and copy due to the
|
||||
// append.
|
||||
dst = aead.Seal(dst[:dlen], nonce, data, aad)
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// decrypt is the decryption function for an AEAD crypter, where aead determines
|
||||
// the type of AEAD crypter, and dst the destination bytes for the decrypted
|
||||
// ciphertext. The dst buffer may fully overlap with plaintext or not at all.
|
||||
func decrypt(aead cipher.AEAD, dst, ciphertext, nonce, aad []byte) ([]byte, error) {
|
||||
if len(nonce) != NonceSize {
|
||||
return nil, fmt.Errorf("nonce size must be %d bytes. received: %d", NonceSize, len(nonce))
|
||||
}
|
||||
// If dst is equal to ciphertext[:0], ciphertext storage is reused.
|
||||
plaintext, err := aead.Open(dst, nonce, ciphertext, aad)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("message auth failed: %v", err)
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
98
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/ciphersuite.go
generated
vendored
Normal file
98
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/ciphersuite.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package halfconn
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
s2apb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
"github.com/google/s2a-go/internal/record/internal/aeadcrypter"
|
||||
)
|
||||
|
||||
// ciphersuite is the interface for retrieving ciphersuite-specific information
|
||||
// and utilities.
|
||||
type ciphersuite interface {
|
||||
// keySize returns the key size in bytes. This refers to the key used by
|
||||
// the AEAD crypter. This is derived by calling HKDF expand on the traffic
|
||||
// secret.
|
||||
keySize() int
|
||||
// nonceSize returns the nonce size in bytes.
|
||||
nonceSize() int
|
||||
// trafficSecretSize returns the traffic secret size in bytes. This refers
|
||||
// to the secret used to derive the traffic key and nonce, as specified in
|
||||
// https://tools.ietf.org/html/rfc8446#section-7.
|
||||
trafficSecretSize() int
|
||||
// hashFunction returns the hash function for the ciphersuite.
|
||||
hashFunction() func() hash.Hash
|
||||
// aeadCrypter takes a key and creates an AEAD crypter for the ciphersuite
|
||||
// using that key.
|
||||
aeadCrypter(key []byte) (aeadcrypter.S2AAEADCrypter, error)
|
||||
}
|
||||
|
||||
func newCiphersuite(ciphersuite s2apb.Ciphersuite) (ciphersuite, error) {
|
||||
switch ciphersuite {
|
||||
case s2apb.Ciphersuite_AES_128_GCM_SHA256:
|
||||
return &aesgcm128sha256{}, nil
|
||||
case s2apb.Ciphersuite_AES_256_GCM_SHA384:
|
||||
return &aesgcm256sha384{}, nil
|
||||
case s2apb.Ciphersuite_CHACHA20_POLY1305_SHA256:
|
||||
return &chachapolysha256{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized ciphersuite: %v", ciphersuite)
|
||||
}
|
||||
}
|
||||
|
||||
// aesgcm128sha256 is the AES-128-GCM-SHA256 implementation of the ciphersuite
|
||||
// interface.
|
||||
type aesgcm128sha256 struct{}
|
||||
|
||||
func (aesgcm128sha256) keySize() int { return aeadcrypter.AES128GCMKeySize }
|
||||
func (aesgcm128sha256) nonceSize() int { return aeadcrypter.NonceSize }
|
||||
func (aesgcm128sha256) trafficSecretSize() int { return aeadcrypter.SHA256DigestSize }
|
||||
func (aesgcm128sha256) hashFunction() func() hash.Hash { return sha256.New }
|
||||
func (aesgcm128sha256) aeadCrypter(key []byte) (aeadcrypter.S2AAEADCrypter, error) {
|
||||
return aeadcrypter.NewAESGCM(key)
|
||||
}
|
||||
|
||||
// aesgcm256sha384 is the AES-256-GCM-SHA384 implementation of the ciphersuite
|
||||
// interface.
|
||||
type aesgcm256sha384 struct{}
|
||||
|
||||
func (aesgcm256sha384) keySize() int { return aeadcrypter.AES256GCMKeySize }
|
||||
func (aesgcm256sha384) nonceSize() int { return aeadcrypter.NonceSize }
|
||||
func (aesgcm256sha384) trafficSecretSize() int { return aeadcrypter.SHA384DigestSize }
|
||||
func (aesgcm256sha384) hashFunction() func() hash.Hash { return sha512.New384 }
|
||||
func (aesgcm256sha384) aeadCrypter(key []byte) (aeadcrypter.S2AAEADCrypter, error) {
|
||||
return aeadcrypter.NewAESGCM(key)
|
||||
}
|
||||
|
||||
// chachapolysha256 is the ChaChaPoly-SHA256 implementation of the ciphersuite
|
||||
// interface.
|
||||
type chachapolysha256 struct{}
|
||||
|
||||
func (chachapolysha256) keySize() int { return aeadcrypter.Chacha20Poly1305KeySize }
|
||||
func (chachapolysha256) nonceSize() int { return aeadcrypter.NonceSize }
|
||||
func (chachapolysha256) trafficSecretSize() int { return aeadcrypter.SHA256DigestSize }
|
||||
func (chachapolysha256) hashFunction() func() hash.Hash { return sha256.New }
|
||||
func (chachapolysha256) aeadCrypter(key []byte) (aeadcrypter.S2AAEADCrypter, error) {
|
||||
return aeadcrypter.NewChachaPoly(key)
|
||||
}
|
||||
60
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/counter.go
generated
vendored
Normal file
60
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/counter.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package halfconn
|
||||
|
||||
import "errors"
|
||||
|
||||
// counter is a 64-bit counter.
|
||||
type counter struct {
|
||||
val uint64
|
||||
hasOverflowed bool
|
||||
}
|
||||
|
||||
// newCounter creates a new counter with the initial value set to val.
|
||||
func newCounter(val uint64) counter {
|
||||
return counter{val: val}
|
||||
}
|
||||
|
||||
// value returns the current value of the counter.
|
||||
func (c *counter) value() (uint64, error) {
|
||||
if c.hasOverflowed {
|
||||
return 0, errors.New("counter has overflowed")
|
||||
}
|
||||
return c.val, nil
|
||||
}
|
||||
|
||||
// increment increments the counter and checks for overflow.
|
||||
func (c *counter) increment() {
|
||||
// If the counter is already invalid due to overflow, there is no need to
|
||||
// increase it. We check for the hasOverflowed flag in the call to value().
|
||||
if c.hasOverflowed {
|
||||
return
|
||||
}
|
||||
c.val++
|
||||
if c.val == 0 {
|
||||
c.hasOverflowed = true
|
||||
}
|
||||
}
|
||||
|
||||
// reset sets the counter value to zero and sets the hasOverflowed flag to
|
||||
// false.
|
||||
func (c *counter) reset() {
|
||||
c.val = 0
|
||||
c.hasOverflowed = false
|
||||
}
|
||||
59
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/expander.go
generated
vendored
Normal file
59
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/expander.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package halfconn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// hkdfExpander is the interface for the HKDF expansion function; see
|
||||
// https://tools.ietf.org/html/rfc5869 for details. its use in TLS 1.3 is
|
||||
// specified in https://tools.ietf.org/html/rfc8446#section-7.2
|
||||
type hkdfExpander interface {
|
||||
// expand takes a secret, a label, and the output length in bytes, and
|
||||
// returns the resulting expanded key.
|
||||
expand(secret, label []byte, length int) ([]byte, error)
|
||||
}
|
||||
|
||||
// defaultHKDFExpander is the default HKDF expander which uses Go's crypto/hkdf
|
||||
// for HKDF expansion.
|
||||
type defaultHKDFExpander struct {
|
||||
h func() hash.Hash
|
||||
}
|
||||
|
||||
// newDefaultHKDFExpander creates an instance of the default HKDF expander
|
||||
// using the given hash function.
|
||||
func newDefaultHKDFExpander(h func() hash.Hash) hkdfExpander {
|
||||
return &defaultHKDFExpander{h: h}
|
||||
}
|
||||
|
||||
func (d *defaultHKDFExpander) expand(secret, label []byte, length int) ([]byte, error) {
|
||||
outBuf := make([]byte, length)
|
||||
n, err := hkdf.Expand(d.h, secret, label).Read(outBuf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hkdf.Expand.Read failed with error: %v", err)
|
||||
}
|
||||
if n < length {
|
||||
return nil, fmt.Errorf("hkdf.Expand.Read returned unexpected length, got %d, want %d", n, length)
|
||||
}
|
||||
return outBuf, nil
|
||||
}
|
||||
193
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/halfconn.go
generated
vendored
Normal file
193
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/halfconn.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package halfconn manages the inbound or outbound traffic of a TLS 1.3
|
||||
// connection.
|
||||
package halfconn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
s2apb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
"github.com/google/s2a-go/internal/record/internal/aeadcrypter"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// The constants below were taken from Section 7.2 and 7.3 in
|
||||
// https://tools.ietf.org/html/rfc8446#section-7. They are used as the label
|
||||
// in HKDF-Expand-Label.
|
||||
const (
|
||||
tls13Key = "tls13 key"
|
||||
tls13Nonce = "tls13 iv"
|
||||
tls13Update = "tls13 traffic upd"
|
||||
)
|
||||
|
||||
// S2AHalfConnection stores the state of the TLS 1.3 connection in the
|
||||
// inbound or outbound direction.
|
||||
type S2AHalfConnection struct {
|
||||
cs ciphersuite
|
||||
expander hkdfExpander
|
||||
// mutex guards sequence, aeadCrypter, trafficSecret, and nonce.
|
||||
mutex sync.Mutex
|
||||
aeadCrypter aeadcrypter.S2AAEADCrypter
|
||||
sequence counter
|
||||
trafficSecret []byte
|
||||
nonce []byte
|
||||
}
|
||||
|
||||
// New creates a new instance of S2AHalfConnection given a ciphersuite and a
|
||||
// traffic secret.
|
||||
func New(ciphersuite s2apb.Ciphersuite, trafficSecret []byte, sequence uint64) (*S2AHalfConnection, error) {
|
||||
cs, err := newCiphersuite(ciphersuite)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new ciphersuite: %v", ciphersuite)
|
||||
}
|
||||
if cs.trafficSecretSize() != len(trafficSecret) {
|
||||
return nil, fmt.Errorf("supplied traffic secret must be %v bytes, given: %v bytes", cs.trafficSecretSize(), len(trafficSecret))
|
||||
}
|
||||
|
||||
hc := &S2AHalfConnection{cs: cs, expander: newDefaultHKDFExpander(cs.hashFunction()), sequence: newCounter(sequence), trafficSecret: trafficSecret}
|
||||
if err = hc.updateCrypterAndNonce(hc.trafficSecret); err != nil {
|
||||
return nil, fmt.Errorf("failed to create half connection using traffic secret: %v", err)
|
||||
}
|
||||
|
||||
return hc, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts the plaintext and computes the tag of dst and plaintext.
|
||||
// dst and plaintext may fully overlap or not at all. Note that the sequence
|
||||
// number will still be incremented on failure, unless the sequence has
|
||||
// overflowed.
|
||||
func (hc *S2AHalfConnection) Encrypt(dst, plaintext, aad []byte) ([]byte, error) {
|
||||
hc.mutex.Lock()
|
||||
sequence, err := hc.getAndIncrementSequence()
|
||||
if err != nil {
|
||||
hc.mutex.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
nonce := hc.maskedNonce(sequence)
|
||||
crypter := hc.aeadCrypter
|
||||
hc.mutex.Unlock()
|
||||
return crypter.Encrypt(dst, plaintext, nonce, aad)
|
||||
}
|
||||
|
||||
// Decrypt decrypts ciphertext and verifies the tag. dst and ciphertext may
|
||||
// fully overlap or not at all. Note that the sequence number will still be
|
||||
// incremented on failure, unless the sequence has overflowed.
|
||||
func (hc *S2AHalfConnection) Decrypt(dst, ciphertext, aad []byte) ([]byte, error) {
|
||||
hc.mutex.Lock()
|
||||
sequence, err := hc.getAndIncrementSequence()
|
||||
if err != nil {
|
||||
hc.mutex.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
nonce := hc.maskedNonce(sequence)
|
||||
crypter := hc.aeadCrypter
|
||||
hc.mutex.Unlock()
|
||||
return crypter.Decrypt(dst, ciphertext, nonce, aad)
|
||||
}
|
||||
|
||||
// UpdateKey advances the traffic secret key, as specified in
|
||||
// https://tools.ietf.org/html/rfc8446#section-7.2. In addition, it derives
|
||||
// a new key and nonce, and resets the sequence number.
|
||||
func (hc *S2AHalfConnection) UpdateKey() error {
|
||||
hc.mutex.Lock()
|
||||
defer hc.mutex.Unlock()
|
||||
|
||||
var err error
|
||||
hc.trafficSecret, err = hc.deriveSecret(hc.trafficSecret, []byte(tls13Update), hc.cs.trafficSecretSize())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive traffic secret: %v", err)
|
||||
}
|
||||
|
||||
if err = hc.updateCrypterAndNonce(hc.trafficSecret); err != nil {
|
||||
return fmt.Errorf("failed to update half connection: %v", err)
|
||||
}
|
||||
|
||||
hc.sequence.reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// TagSize returns the tag size in bytes of the underlying AEAD crypter.
|
||||
func (hc *S2AHalfConnection) TagSize() int {
|
||||
return hc.aeadCrypter.TagSize()
|
||||
}
|
||||
|
||||
// updateCrypterAndNonce takes a new traffic secret and updates the crypter
|
||||
// and nonce. Note that the mutex must be held while calling this function.
|
||||
func (hc *S2AHalfConnection) updateCrypterAndNonce(newTrafficSecret []byte) error {
|
||||
key, err := hc.deriveSecret(newTrafficSecret, []byte(tls13Key), hc.cs.keySize())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update key: %v", err)
|
||||
}
|
||||
|
||||
hc.nonce, err = hc.deriveSecret(newTrafficSecret, []byte(tls13Nonce), hc.cs.nonceSize())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update nonce: %v", err)
|
||||
}
|
||||
|
||||
hc.aeadCrypter, err = hc.cs.aeadCrypter(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update AEAD crypter: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAndIncrement returns the current sequence number and increments it. Note
|
||||
// that the mutex must be held while calling this function.
|
||||
func (hc *S2AHalfConnection) getAndIncrementSequence() (uint64, error) {
|
||||
sequence, err := hc.sequence.value()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hc.sequence.increment()
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// maskedNonce creates a copy of the nonce that is masked with the sequence
|
||||
// number. Note that the mutex must be held while calling this function.
|
||||
func (hc *S2AHalfConnection) maskedNonce(sequence uint64) []byte {
|
||||
const uint64Size = 8
|
||||
nonce := make([]byte, len(hc.nonce))
|
||||
copy(nonce, hc.nonce)
|
||||
for i := 0; i < uint64Size; i++ {
|
||||
nonce[aeadcrypter.NonceSize-uint64Size+i] ^= byte(sequence >> uint64(56-uint64Size*i))
|
||||
}
|
||||
return nonce
|
||||
}
|
||||
|
||||
// deriveSecret implements the Derive-Secret function, as specified in
|
||||
// https://tools.ietf.org/html/rfc8446#section-7.1.
|
||||
func (hc *S2AHalfConnection) deriveSecret(secret, label []byte, length int) ([]byte, error) {
|
||||
var hkdfLabel cryptobyte.Builder
|
||||
hkdfLabel.AddUint16(uint16(length))
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(label)
|
||||
})
|
||||
// Append an empty `Context` field to the label, as specified in the RFC.
|
||||
// The half connection does not use the `Context` field.
|
||||
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes([]byte(""))
|
||||
})
|
||||
hkdfLabelBytes, err := hkdfLabel.Bytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("deriveSecret failed: %v", err)
|
||||
}
|
||||
return hc.expander.expand(secret, hkdfLabelBytes, length)
|
||||
}
|
||||
729
vendor/github.com/google/s2a-go/internal/record/record.go
generated
vendored
Normal file
729
vendor/github.com/google/s2a-go/internal/record/record.go
generated
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package record implements the TLS 1.3 record protocol used by the S2A
|
||||
// transport credentials.
|
||||
package record
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
"github.com/google/s2a-go/internal/record/internal/halfconn"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// recordType is the `ContentType` as described in
|
||||
// https://tools.ietf.org/html/rfc8446#section-5.1.
|
||||
type recordType byte
|
||||
|
||||
const (
|
||||
alert recordType = 21
|
||||
handshake recordType = 22
|
||||
applicationData recordType = 23
|
||||
)
|
||||
|
||||
// keyUpdateRequest is the `KeyUpdateRequest` as described in
|
||||
// https://tools.ietf.org/html/rfc8446#section-4.6.3.
|
||||
type keyUpdateRequest byte
|
||||
|
||||
const (
|
||||
updateNotRequested keyUpdateRequest = 0
|
||||
updateRequested keyUpdateRequest = 1
|
||||
)
|
||||
|
||||
// alertDescription is the `AlertDescription` as described in
|
||||
// https://tools.ietf.org/html/rfc8446#section-6.
|
||||
type alertDescription byte
|
||||
|
||||
const (
|
||||
closeNotify alertDescription = 0
|
||||
)
|
||||
|
||||
// sessionTicketState is used to determine whether session tickets have not yet
|
||||
// been received, are in the process of being received, or have finished
|
||||
// receiving.
|
||||
type sessionTicketState byte
|
||||
|
||||
const (
|
||||
ticketsNotYetReceived sessionTicketState = 0
|
||||
receivingTickets sessionTicketState = 1
|
||||
notReceivingTickets sessionTicketState = 2
|
||||
)
|
||||
|
||||
const (
|
||||
// The TLS 1.3-specific constants below (tlsRecordMaxPlaintextSize,
|
||||
// tlsRecordHeaderSize, tlsRecordTypeSize) were taken from
|
||||
// https://tools.ietf.org/html/rfc8446#section-5.1.
|
||||
|
||||
// tlsRecordMaxPlaintextSize is the maximum size in bytes of the plaintext
|
||||
// in a single TLS 1.3 record.
|
||||
tlsRecordMaxPlaintextSize = 16384 // 2^14
|
||||
// tlsRecordTypeSize is the size in bytes of the TLS 1.3 record type.
|
||||
tlsRecordTypeSize = 1
|
||||
// tlsTagSize is the size in bytes of the tag of the following three
|
||||
// ciphersuites: AES-128-GCM-SHA256, AES-256-GCM-SHA384,
|
||||
// CHACHA20-POLY1305-SHA256.
|
||||
tlsTagSize = 16
|
||||
// tlsRecordMaxPayloadSize is the maximum size in bytes of the payload in a
|
||||
// single TLS 1.3 record. This is the maximum size of the plaintext plus the
|
||||
// record type byte and 16 bytes of the tag.
|
||||
tlsRecordMaxPayloadSize = tlsRecordMaxPlaintextSize + tlsRecordTypeSize + tlsTagSize
|
||||
// tlsRecordHeaderTypeSize is the size in bytes of the TLS 1.3 record
|
||||
// header type.
|
||||
tlsRecordHeaderTypeSize = 1
|
||||
// tlsRecordHeaderLegacyRecordVersionSize is the size in bytes of the TLS
|
||||
// 1.3 record header legacy record version.
|
||||
tlsRecordHeaderLegacyRecordVersionSize = 2
|
||||
// tlsRecordHeaderPayloadLengthSize is the size in bytes of the TLS 1.3
|
||||
// record header payload length.
|
||||
tlsRecordHeaderPayloadLengthSize = 2
|
||||
// tlsRecordHeaderSize is the size in bytes of the TLS 1.3 record header.
|
||||
tlsRecordHeaderSize = tlsRecordHeaderTypeSize + tlsRecordHeaderLegacyRecordVersionSize + tlsRecordHeaderPayloadLengthSize
|
||||
// tlsRecordMaxSize
|
||||
tlsRecordMaxSize = tlsRecordMaxPayloadSize + tlsRecordHeaderSize
|
||||
// tlsApplicationData is the application data type of the TLS 1.3 record
|
||||
// header.
|
||||
tlsApplicationData = 23
|
||||
// tlsLegacyRecordVersion is the legacy record version of the TLS record.
|
||||
tlsLegacyRecordVersion = 3
|
||||
// tlsAlertSize is the size in bytes of an alert of TLS 1.3.
|
||||
tlsAlertSize = 2
|
||||
)
|
||||
|
||||
const (
|
||||
// These are TLS 1.3 handshake-specific constants.
|
||||
|
||||
// tlsHandshakeNewSessionTicketType is the prefix of a handshake new session
|
||||
// ticket message of TLS 1.3.
|
||||
tlsHandshakeNewSessionTicketType = 4
|
||||
// tlsHandshakeKeyUpdateType is the prefix of a handshake key update message
|
||||
// of TLS 1.3.
|
||||
tlsHandshakeKeyUpdateType = 24
|
||||
// tlsHandshakeMsgTypeSize is the size in bytes of the TLS 1.3 handshake
|
||||
// message type field.
|
||||
tlsHandshakeMsgTypeSize = 1
|
||||
// tlsHandshakeLengthSize is the size in bytes of the TLS 1.3 handshake
|
||||
// message length field.
|
||||
tlsHandshakeLengthSize = 3
|
||||
// tlsHandshakeKeyUpdateMsgSize is the size in bytes of the TLS 1.3
|
||||
// handshake key update message.
|
||||
tlsHandshakeKeyUpdateMsgSize = 1
|
||||
// tlsHandshakePrefixSize is the size in bytes of the prefix of the TLS 1.3
|
||||
// handshake message.
|
||||
tlsHandshakePrefixSize = 4
|
||||
// tlsMaxSessionTicketSize is the maximum size of a NewSessionTicket message
|
||||
// in TLS 1.3. This is the sum of the max sizes of all the fields in the
|
||||
// NewSessionTicket struct specified in
|
||||
// https://tools.ietf.org/html/rfc8446#section-4.6.1.
|
||||
tlsMaxSessionTicketSize = 131338
|
||||
)
|
||||
|
||||
const (
|
||||
// outBufMaxRecords is the maximum number of records that can fit in the
|
||||
// ourRecordsBuf buffer.
|
||||
outBufMaxRecords = 16
|
||||
// outBufMaxSize is the maximum size (in bytes) of the outRecordsBuf buffer.
|
||||
outBufMaxSize = outBufMaxRecords * tlsRecordMaxSize
|
||||
// maxAllowedTickets is the maximum number of session tickets that are
|
||||
// allowed. The number of tickets are limited to ensure that the size of the
|
||||
// ticket queue does not grow indefinitely. S2A also keeps a limit on the
|
||||
// number of tickets that it caches.
|
||||
maxAllowedTickets = 5
|
||||
)
|
||||
|
||||
// preConstructedKeyUpdateMsg holds the key update message. This is needed as an
|
||||
// optimization so that the same message does not need to be constructed every
|
||||
// time a key update message is sent.
|
||||
var preConstructedKeyUpdateMsg = buildKeyUpdateRequest()
|
||||
|
||||
// conn represents a secured TLS connection. It implements the net.Conn
|
||||
// interface.
|
||||
type conn struct {
|
||||
net.Conn
|
||||
// inConn is the half connection responsible for decrypting incoming bytes.
|
||||
inConn *halfconn.S2AHalfConnection
|
||||
// outConn is the half connection responsible for encrypting outgoing bytes.
|
||||
outConn *halfconn.S2AHalfConnection
|
||||
// pendingApplicationData holds data that has been read from the connection
|
||||
// and decrypted, but has not yet been returned by Read.
|
||||
pendingApplicationData []byte
|
||||
// unusedBuf holds data read from the network that has not yet been
|
||||
// decrypted. This data might not consist of a complete record. It may
|
||||
// consist of several records, the last of which could be incomplete.
|
||||
unusedBuf []byte
|
||||
// outRecordsBuf is a buffer used to store outgoing TLS records before
|
||||
// they are written to the network.
|
||||
outRecordsBuf []byte
|
||||
// nextRecord stores the next record info in the unusedBuf buffer.
|
||||
nextRecord []byte
|
||||
// overheadSize is the overhead size in bytes of each TLS 1.3 record, which
|
||||
// is computed as overheadSize = header size + record type byte + tag size.
|
||||
// Note that there is no padding by zeros in the overhead calculation.
|
||||
overheadSize int
|
||||
// readMutex guards against concurrent calls to Read. This is required since
|
||||
// Close may be called during a Read.
|
||||
readMutex sync.Mutex
|
||||
// writeMutex guards against concurrent calls to Write. This is required
|
||||
// since Close may be called during a Write, and also because a key update
|
||||
// message may be written during a Read.
|
||||
writeMutex sync.Mutex
|
||||
// handshakeBuf holds handshake messages while they are being processed.
|
||||
handshakeBuf []byte
|
||||
// ticketState is the current processing state of the session tickets.
|
||||
ticketState sessionTicketState
|
||||
// sessionTickets holds the completed session tickets until they are sent to
|
||||
// the handshaker service for processing.
|
||||
sessionTickets [][]byte
|
||||
// ticketSender sends session tickets to the S2A handshaker service.
|
||||
ticketSender s2aTicketSender
|
||||
// callComplete is a channel that blocks closing the record protocol until a
|
||||
// pending call to the S2A completes.
|
||||
callComplete chan bool
|
||||
}
|
||||
|
||||
// ConnParameters holds the parameters used for creating a new conn object.
|
||||
type ConnParameters struct {
|
||||
// NetConn is the TCP connection to the peer. This parameter is required.
|
||||
NetConn net.Conn
|
||||
// Ciphersuite is the TLS ciphersuite negotiated by the S2A handshaker
|
||||
// service. This parameter is required.
|
||||
Ciphersuite commonpb.Ciphersuite
|
||||
// TLSVersion is the TLS version number negotiated by the S2A handshaker
|
||||
// service. This parameter is required.
|
||||
TLSVersion commonpb.TLSVersion
|
||||
// InTrafficSecret is the traffic secret used to derive the session key for
|
||||
// the inbound direction. This parameter is required.
|
||||
InTrafficSecret []byte
|
||||
// OutTrafficSecret is the traffic secret used to derive the session key
|
||||
// for the outbound direction. This parameter is required.
|
||||
OutTrafficSecret []byte
|
||||
// UnusedBuf is the data read from the network that has not yet been
|
||||
// decrypted. This parameter is optional. If not provided, then no
|
||||
// application data was sent in the same flight of messages as the final
|
||||
// handshake message.
|
||||
UnusedBuf []byte
|
||||
// InSequence is the sequence number of the next, incoming, TLS record.
|
||||
// This parameter is required.
|
||||
InSequence uint64
|
||||
// OutSequence is the sequence number of the next, outgoing, TLS record.
|
||||
// This parameter is required.
|
||||
OutSequence uint64
|
||||
// HSAddr stores the address of the S2A handshaker service. This parameter
|
||||
// is optional. If not provided, then TLS resumption is disabled.
|
||||
HSAddr string
|
||||
// ConnectionId is the connection identifier that was created and sent by
|
||||
// S2A at the end of a handshake.
|
||||
ConnectionID uint64
|
||||
// LocalIdentity is the local identity that was used by S2A during session
|
||||
// setup and included in the session result.
|
||||
LocalIdentity *commonpb.Identity
|
||||
// EnsureProcessSessionTickets allows users to wait and ensure that all
|
||||
// available session tickets are sent to S2A before a process completes.
|
||||
EnsureProcessSessionTickets *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewConn creates a TLS record protocol that wraps the TCP connection.
|
||||
func NewConn(o *ConnParameters) (net.Conn, error) {
|
||||
if o == nil {
|
||||
return nil, errors.New("conn options must not be nil")
|
||||
}
|
||||
if o.TLSVersion != commonpb.TLSVersion_TLS1_3 {
|
||||
return nil, errors.New("TLS version must be TLS 1.3")
|
||||
}
|
||||
|
||||
inConn, err := halfconn.New(o.Ciphersuite, o.InTrafficSecret, o.InSequence)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create inbound half connection: %v", err)
|
||||
}
|
||||
outConn, err := halfconn.New(o.Ciphersuite, o.OutTrafficSecret, o.OutSequence)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create outbound half connection: %v", err)
|
||||
}
|
||||
|
||||
// The tag size for the in/out connections should be the same.
|
||||
overheadSize := tlsRecordHeaderSize + tlsRecordTypeSize + inConn.TagSize()
|
||||
var unusedBuf []byte
|
||||
if o.UnusedBuf == nil {
|
||||
// We pre-allocate unusedBuf to be of size
|
||||
// 2*tlsRecordMaxSize-1 during initialization. We only read from the
|
||||
// network into unusedBuf when unusedBuf does not contain a complete
|
||||
// record and the incomplete record is at most tlsRecordMaxSize-1
|
||||
// (bytes). And we read at most tlsRecordMaxSize bytes of data from the
|
||||
// network into unusedBuf at one time. Therefore, 2*tlsRecordMaxSize-1
|
||||
// is large enough to buffer data read from the network.
|
||||
unusedBuf = make([]byte, 0, 2*tlsRecordMaxSize-1)
|
||||
} else {
|
||||
unusedBuf = make([]byte, len(o.UnusedBuf))
|
||||
copy(unusedBuf, o.UnusedBuf)
|
||||
}
|
||||
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
grpclog.Infof("failed to create single token access token manager: %v", err)
|
||||
}
|
||||
|
||||
s2aConn := &conn{
|
||||
Conn: o.NetConn,
|
||||
inConn: inConn,
|
||||
outConn: outConn,
|
||||
unusedBuf: unusedBuf,
|
||||
outRecordsBuf: make([]byte, tlsRecordMaxSize),
|
||||
nextRecord: unusedBuf,
|
||||
overheadSize: overheadSize,
|
||||
ticketState: ticketsNotYetReceived,
|
||||
// Pre-allocate the buffer for one session ticket message and the max
|
||||
// plaintext size. This is the largest size that handshakeBuf will need
|
||||
// to hold. The largest incomplete handshake message is the
|
||||
// [handshake header size] + [max session ticket size] - 1.
|
||||
// Then, tlsRecordMaxPlaintextSize is the maximum size that will be
|
||||
// appended to the handshakeBuf before the handshake message is
|
||||
// completed. Therefore, the buffer size below should be large enough to
|
||||
// buffer any handshake messages.
|
||||
handshakeBuf: make([]byte, 0, tlsHandshakePrefixSize+tlsMaxSessionTicketSize+tlsRecordMaxPlaintextSize-1),
|
||||
ticketSender: &ticketSender{
|
||||
hsAddr: o.HSAddr,
|
||||
connectionID: o.ConnectionID,
|
||||
localIdentity: o.LocalIdentity,
|
||||
tokenManager: tokenManager,
|
||||
ensureProcessSessionTickets: o.EnsureProcessSessionTickets,
|
||||
},
|
||||
callComplete: make(chan bool),
|
||||
}
|
||||
return s2aConn, nil
|
||||
}
|
||||
|
||||
// Read reads and decrypts a TLS 1.3 record from the underlying connection, and
|
||||
// copies any application data received from the peer into b. If the size of the
|
||||
// payload is greater than len(b), Read retains the remaining bytes in an
|
||||
// internal buffer, and subsequent calls to Read will read from this buffer
|
||||
// until it is exhausted. At most 1 TLS record worth of application data is
|
||||
// written to b for each call to Read.
|
||||
//
|
||||
// Note that for the user to efficiently call this method, the user should
|
||||
// ensure that the buffer b is allocated such that the buffer does not have any
|
||||
// unused segments. This can be done by calling Read via io.ReadFull, which
|
||||
// continually calls Read until the specified buffer has been filled. Also note
|
||||
// that the user should close the connection via Close() if an error is thrown
|
||||
// by a call to Read.
|
||||
func (p *conn) Read(b []byte) (n int, err error) {
|
||||
p.readMutex.Lock()
|
||||
defer p.readMutex.Unlock()
|
||||
// Check if p.pendingApplication data has leftover application data from
|
||||
// the previous call to Read.
|
||||
if len(p.pendingApplicationData) == 0 {
|
||||
// Read a full record from the wire.
|
||||
record, err := p.readFullRecord()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Now we have a complete record, so split the header and validate it
|
||||
// The TLS record is split into 2 pieces: the record header and the
|
||||
// payload. The payload has the following form:
|
||||
// [payload] = [ciphertext of application data]
|
||||
// + [ciphertext of record type byte]
|
||||
// + [(optionally) ciphertext of padding by zeros]
|
||||
// + [tag]
|
||||
header, payload, err := splitAndValidateHeader(record)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Decrypt the ciphertext.
|
||||
p.pendingApplicationData, err = p.inConn.Decrypt(payload[:0], payload, header)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Remove the padding by zeros and the record type byte from the
|
||||
// p.pendingApplicationData buffer.
|
||||
msgType, err := p.stripPaddingAndType()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Check that the length of the plaintext after stripping the padding
|
||||
// and record type byte is under the maximum plaintext size.
|
||||
if len(p.pendingApplicationData) > tlsRecordMaxPlaintextSize {
|
||||
return 0, errors.New("plaintext size larger than maximum")
|
||||
}
|
||||
// The expected message types are application data, alert, and
|
||||
// handshake. For application data, the bytes are directly copied into
|
||||
// b. For an alert, the type of the alert is checked and the connection
|
||||
// is closed on a close notify alert. For a handshake message, the
|
||||
// handshake message type is checked. The handshake message type can be
|
||||
// a key update type, for which we advance the traffic secret, and a
|
||||
// new session ticket type, for which we send the received ticket to S2A
|
||||
// for processing.
|
||||
switch msgType {
|
||||
case applicationData:
|
||||
if len(p.handshakeBuf) > 0 {
|
||||
return 0, errors.New("application data received while processing fragmented handshake messages")
|
||||
}
|
||||
case alert:
|
||||
return 0, p.handleAlertMessage()
|
||||
case handshake:
|
||||
if err = p.handleHandshakeMessage(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
default:
|
||||
return 0, errors.New("unknown record type")
|
||||
}
|
||||
}
|
||||
// Write as much application data as possible to b, the output buffer.
|
||||
n = copy(b, p.pendingApplicationData)
|
||||
p.pendingApplicationData = p.pendingApplicationData[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write divides b into segments of size tlsRecordMaxPlaintextSize, builds a
|
||||
// TLS 1.3 record (of type "application data") from each segment, and sends
|
||||
// the record to the peer. It returns the number of plaintext bytes that were
|
||||
// successfully sent to the peer.
|
||||
func (p *conn) Write(b []byte) (n int, err error) {
|
||||
p.writeMutex.Lock()
|
||||
defer p.writeMutex.Unlock()
|
||||
return p.writeTLSRecord(b, tlsApplicationData)
|
||||
}
|
||||
|
||||
// writeTLSRecord divides b into segments of size maxPlaintextBytesPerRecord,
|
||||
// builds a TLS 1.3 record (of type recordType) from each segment, and sends
|
||||
// the record to the peer. It returns the number of plaintext bytes that were
|
||||
// successfully sent to the peer.
|
||||
func (p *conn) writeTLSRecord(b []byte, recordType byte) (n int, err error) {
|
||||
// Create a record of only header, record type, and tag if given empty
|
||||
// byte array.
|
||||
if len(b) == 0 {
|
||||
recordEndIndex, _, err := p.buildRecord(b, recordType, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Write the bytes stored in outRecordsBuf to p.Conn. Since we return
|
||||
// the number of plaintext bytes written without overhead, we will
|
||||
// always return 0 while p.Conn.Write returns the entire record length.
|
||||
_, err = p.Conn.Write(p.outRecordsBuf[:recordEndIndex])
|
||||
return 0, err
|
||||
}
|
||||
|
||||
numRecords := int(math.Ceil(float64(len(b)) / float64(tlsRecordMaxPlaintextSize)))
|
||||
totalRecordsSize := len(b) + numRecords*p.overheadSize
|
||||
partialBSize := len(b)
|
||||
if totalRecordsSize > outBufMaxSize {
|
||||
totalRecordsSize = outBufMaxSize
|
||||
partialBSize = outBufMaxRecords * tlsRecordMaxPlaintextSize
|
||||
}
|
||||
if len(p.outRecordsBuf) < totalRecordsSize {
|
||||
p.outRecordsBuf = make([]byte, totalRecordsSize)
|
||||
}
|
||||
for bStart := 0; bStart < len(b); bStart += partialBSize {
|
||||
bEnd := bStart + partialBSize
|
||||
if bEnd > len(b) {
|
||||
bEnd = len(b)
|
||||
}
|
||||
partialB := b[bStart:bEnd]
|
||||
recordEndIndex := 0
|
||||
for len(partialB) > 0 {
|
||||
recordEndIndex, partialB, err = p.buildRecord(partialB, recordType, recordEndIndex)
|
||||
if err != nil {
|
||||
// Return the amount of bytes written prior to the error.
|
||||
return bStart, err
|
||||
}
|
||||
}
|
||||
// Write the bytes stored in outRecordsBuf to p.Conn. If there is an
|
||||
// error, calculate the total number of plaintext bytes of complete
|
||||
// records successfully written to the peer and return it.
|
||||
nn, err := p.Conn.Write(p.outRecordsBuf[:recordEndIndex])
|
||||
if err != nil {
|
||||
numberOfCompletedRecords := int(math.Floor(float64(nn) / float64(tlsRecordMaxSize)))
|
||||
return bStart + numberOfCompletedRecords*tlsRecordMaxPlaintextSize, err
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// buildRecord builds a TLS 1.3 record of type recordType from plaintext,
|
||||
// and writes the record to outRecordsBuf at recordStartIndex. The record will
|
||||
// have at most tlsRecordMaxPlaintextSize bytes of payload. It returns the
|
||||
// index of outRecordsBuf where the current record ends, as well as any
|
||||
// remaining plaintext bytes.
|
||||
func (p *conn) buildRecord(plaintext []byte, recordType byte, recordStartIndex int) (n int, remainingPlaintext []byte, err error) {
|
||||
// Construct the payload, which consists of application data and record type.
|
||||
dataLen := len(plaintext)
|
||||
if dataLen > tlsRecordMaxPlaintextSize {
|
||||
dataLen = tlsRecordMaxPlaintextSize
|
||||
}
|
||||
remainingPlaintext = plaintext[dataLen:]
|
||||
newRecordBuf := p.outRecordsBuf[recordStartIndex:]
|
||||
|
||||
copy(newRecordBuf[tlsRecordHeaderSize:], plaintext[:dataLen])
|
||||
newRecordBuf[tlsRecordHeaderSize+dataLen] = recordType
|
||||
payload := newRecordBuf[tlsRecordHeaderSize : tlsRecordHeaderSize+dataLen+1] // 1 is for the recordType.
|
||||
// Construct the header.
|
||||
newRecordBuf[0] = tlsApplicationData
|
||||
newRecordBuf[1] = tlsLegacyRecordVersion
|
||||
newRecordBuf[2] = tlsLegacyRecordVersion
|
||||
binary.BigEndian.PutUint16(newRecordBuf[3:], uint16(len(payload)+tlsTagSize))
|
||||
header := newRecordBuf[:tlsRecordHeaderSize]
|
||||
|
||||
// Encrypt the payload using header as aad.
|
||||
encryptedPayload, err := p.outConn.Encrypt(newRecordBuf[tlsRecordHeaderSize:][:0], payload, header)
|
||||
if err != nil {
|
||||
return 0, plaintext, err
|
||||
}
|
||||
recordStartIndex += len(header) + len(encryptedPayload)
|
||||
return recordStartIndex, remainingPlaintext, nil
|
||||
}
|
||||
|
||||
func (p *conn) Close() error {
|
||||
// Close the connection immediately.
|
||||
return p.Conn.Close()
|
||||
}
|
||||
|
||||
// stripPaddingAndType strips the padding by zeros and record type from
|
||||
// p.pendingApplicationData and returns the record type. Note that
|
||||
// p.pendingApplicationData should be of the form:
|
||||
// [application data] + [record type byte] + [trailing zeros]
|
||||
func (p *conn) stripPaddingAndType() (recordType, error) {
|
||||
if len(p.pendingApplicationData) == 0 {
|
||||
return 0, errors.New("application data had length 0")
|
||||
}
|
||||
i := len(p.pendingApplicationData) - 1
|
||||
// Search for the index of the record type byte.
|
||||
for i > 0 {
|
||||
if p.pendingApplicationData[i] != 0 {
|
||||
break
|
||||
}
|
||||
i--
|
||||
}
|
||||
rt := recordType(p.pendingApplicationData[i])
|
||||
p.pendingApplicationData = p.pendingApplicationData[:i]
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// readFullRecord reads from the wire until a record is completed and returns
|
||||
// the full record.
|
||||
func (p *conn) readFullRecord() (fullRecord []byte, err error) {
|
||||
fullRecord, p.nextRecord, err = parseReadBuffer(p.nextRecord, tlsRecordMaxPayloadSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check whether the next record to be decrypted has been completely
|
||||
// received.
|
||||
if len(fullRecord) == 0 {
|
||||
copy(p.unusedBuf, p.nextRecord)
|
||||
p.unusedBuf = p.unusedBuf[:len(p.nextRecord)]
|
||||
// Always copy next incomplete record to the beginning of the
|
||||
// unusedBuf buffer and reset nextRecord to it.
|
||||
p.nextRecord = p.unusedBuf
|
||||
}
|
||||
// Keep reading from the wire until we have a complete record.
|
||||
for len(fullRecord) == 0 {
|
||||
if len(p.unusedBuf) == cap(p.unusedBuf) {
|
||||
tmp := make([]byte, len(p.unusedBuf), cap(p.unusedBuf)+tlsRecordMaxSize)
|
||||
copy(tmp, p.unusedBuf)
|
||||
p.unusedBuf = tmp
|
||||
}
|
||||
n, err := p.Conn.Read(p.unusedBuf[len(p.unusedBuf):min(cap(p.unusedBuf), len(p.unusedBuf)+tlsRecordMaxSize)])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.unusedBuf = p.unusedBuf[:len(p.unusedBuf)+n]
|
||||
fullRecord, p.nextRecord, err = parseReadBuffer(p.unusedBuf, tlsRecordMaxPayloadSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return fullRecord, nil
|
||||
}
|
||||
|
||||
// parseReadBuffer parses the provided buffer and returns a full record and any
|
||||
// remaining bytes in that buffer. If the record is incomplete, nil is returned
|
||||
// for the first return value and the given byte buffer is returned for the
|
||||
// second return value. The length of the payload specified by the header should
|
||||
// not be greater than maxLen, otherwise an error is returned. Note that this
|
||||
// function does not allocate or copy any buffers.
|
||||
func parseReadBuffer(b []byte, maxLen uint16) (fullRecord, remaining []byte, err error) {
|
||||
// If the header is not complete, return the provided buffer as remaining
|
||||
// buffer.
|
||||
if len(b) < tlsRecordHeaderSize {
|
||||
return nil, b, nil
|
||||
}
|
||||
msgLenField := b[tlsRecordHeaderTypeSize+tlsRecordHeaderLegacyRecordVersionSize : tlsRecordHeaderSize]
|
||||
length := binary.BigEndian.Uint16(msgLenField)
|
||||
if length > maxLen {
|
||||
return nil, nil, fmt.Errorf("record length larger than the limit %d", maxLen)
|
||||
}
|
||||
if len(b) < int(length)+tlsRecordHeaderSize {
|
||||
// Record is not complete yet.
|
||||
return nil, b, nil
|
||||
}
|
||||
return b[:tlsRecordHeaderSize+length], b[tlsRecordHeaderSize+length:], nil
|
||||
}
|
||||
|
||||
// splitAndValidateHeader splits the header from the payload in the TLS 1.3
|
||||
// record and returns them. Note that the header is checked for validity, and an
|
||||
// error is returned when an invalid header is parsed. Also note that this
|
||||
// function does not allocate or copy any buffers.
|
||||
func splitAndValidateHeader(record []byte) (header, payload []byte, err error) {
|
||||
if len(record) < tlsRecordHeaderSize {
|
||||
return nil, nil, fmt.Errorf("record was smaller than the header size")
|
||||
}
|
||||
header = record[:tlsRecordHeaderSize]
|
||||
payload = record[tlsRecordHeaderSize:]
|
||||
if header[0] != tlsApplicationData {
|
||||
return nil, nil, fmt.Errorf("incorrect type in the header")
|
||||
}
|
||||
// Check the legacy record version, which should be 0x03, 0x03.
|
||||
if header[1] != 0x03 || header[2] != 0x03 {
|
||||
return nil, nil, fmt.Errorf("incorrect legacy record version in the header")
|
||||
}
|
||||
return header, payload, nil
|
||||
}
|
||||
|
||||
// handleAlertMessage handles an alert message.
|
||||
func (p *conn) handleAlertMessage() error {
|
||||
if len(p.pendingApplicationData) != tlsAlertSize {
|
||||
return errors.New("invalid alert message size")
|
||||
}
|
||||
alertType := p.pendingApplicationData[1]
|
||||
// Clear the body of the alert message.
|
||||
p.pendingApplicationData = p.pendingApplicationData[:0]
|
||||
if alertType == byte(closeNotify) {
|
||||
return errors.New("received a close notify alert")
|
||||
}
|
||||
// TODO(matthewstevenson88): Add support for more alert types.
|
||||
return fmt.Errorf("received an unrecognized alert type: %v", alertType)
|
||||
}
|
||||
|
||||
// parseHandshakeHeader parses a handshake message from the handshake buffer.
|
||||
// It returns the message type, the message length, the message, the raw message
|
||||
// that includes the type and length bytes and a flag indicating whether the
|
||||
// handshake message has been fully parsed. i.e. whether the entire handshake
|
||||
// message was in the handshake buffer.
|
||||
func (p *conn) parseHandshakeMsg() (msgType byte, msgLen uint32, msg []byte, rawMsg []byte, ok bool) {
|
||||
// Handle the case where the 4 byte handshake header is fragmented.
|
||||
if len(p.handshakeBuf) < tlsHandshakePrefixSize {
|
||||
return 0, 0, nil, nil, false
|
||||
}
|
||||
msgType = p.handshakeBuf[0]
|
||||
msgLen = bigEndianInt24(p.handshakeBuf[tlsHandshakeMsgTypeSize : tlsHandshakeMsgTypeSize+tlsHandshakeLengthSize])
|
||||
if msgLen > uint32(len(p.handshakeBuf)-tlsHandshakePrefixSize) {
|
||||
return 0, 0, nil, nil, false
|
||||
}
|
||||
msg = p.handshakeBuf[tlsHandshakePrefixSize : tlsHandshakePrefixSize+msgLen]
|
||||
rawMsg = p.handshakeBuf[:tlsHandshakeMsgTypeSize+tlsHandshakeLengthSize+msgLen]
|
||||
p.handshakeBuf = p.handshakeBuf[tlsHandshakePrefixSize+msgLen:]
|
||||
return msgType, msgLen, msg, rawMsg, true
|
||||
}
|
||||
|
||||
// handleHandshakeMessage handles a handshake message. Note that the first
|
||||
// complete handshake message from the handshake buffer is removed, if it
|
||||
// exists.
|
||||
func (p *conn) handleHandshakeMessage() error {
|
||||
// Copy the pending application data to the handshake buffer. At this point,
|
||||
// we are guaranteed that the pending application data contains only parts
|
||||
// of a handshake message.
|
||||
p.handshakeBuf = append(p.handshakeBuf, p.pendingApplicationData...)
|
||||
p.pendingApplicationData = p.pendingApplicationData[:0]
|
||||
// Several handshake messages may be coalesced into a single record.
|
||||
// Continue reading them until the handshake buffer is empty.
|
||||
for len(p.handshakeBuf) > 0 {
|
||||
handshakeMsgType, msgLen, msg, _, ok := p.parseHandshakeMsg()
|
||||
if !ok {
|
||||
// The handshake could not be fully parsed, so read in another
|
||||
// record and try again later.
|
||||
break
|
||||
}
|
||||
switch handshakeMsgType {
|
||||
case tlsHandshakeKeyUpdateType:
|
||||
if msgLen != tlsHandshakeKeyUpdateMsgSize {
|
||||
return errors.New("invalid handshake key update message length")
|
||||
}
|
||||
if len(p.handshakeBuf) != 0 {
|
||||
return errors.New("key update message must be the last message of a handshake record")
|
||||
}
|
||||
if err := p.handleKeyUpdateMsg(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
case tlsHandshakeNewSessionTicketType:
|
||||
// Do nothing for session ticket.
|
||||
default:
|
||||
return errors.New("unknown handshake message type")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildKeyUpdateRequest() []byte {
|
||||
b := make([]byte, tlsHandshakePrefixSize+tlsHandshakeKeyUpdateMsgSize)
|
||||
b[0] = tlsHandshakeKeyUpdateType
|
||||
b[1] = 0
|
||||
b[2] = 0
|
||||
b[3] = tlsHandshakeKeyUpdateMsgSize
|
||||
b[4] = byte(updateNotRequested)
|
||||
return b
|
||||
}
|
||||
|
||||
// handleKeyUpdateMsg handles a key update message.
|
||||
func (p *conn) handleKeyUpdateMsg(msg []byte) error {
|
||||
keyUpdateRequest := msg[0]
|
||||
if keyUpdateRequest != byte(updateNotRequested) &&
|
||||
keyUpdateRequest != byte(updateRequested) {
|
||||
return errors.New("invalid handshake key update message")
|
||||
}
|
||||
if err := p.inConn.UpdateKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Send a key update message back to the peer if requested.
|
||||
if keyUpdateRequest == byte(updateRequested) {
|
||||
p.writeMutex.Lock()
|
||||
defer p.writeMutex.Unlock()
|
||||
n, err := p.writeTLSRecord(preConstructedKeyUpdateMsg, byte(handshake))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != tlsHandshakePrefixSize+tlsHandshakeKeyUpdateMsgSize {
|
||||
return errors.New("key update request message wrote less bytes than expected")
|
||||
}
|
||||
if err = p.outConn.UpdateKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bidEndianInt24 converts the given byte buffer of at least size 3 and
|
||||
// outputs the resulting 24 bit integer as a uint32. This is needed because
|
||||
// TLS 1.3 requires 3 byte integers, and the binary.BigEndian package does
|
||||
// not provide a way to transform a byte buffer into a 3 byte integer.
|
||||
func bigEndianInt24(b []byte) uint32 {
|
||||
_ = b[2] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
178
vendor/github.com/google/s2a-go/internal/record/ticketsender.go
generated
vendored
Normal file
178
vendor/github.com/google/s2a-go/internal/record/ticketsender.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package record
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/s2a-go/internal/handshaker/service"
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
s2apb "github.com/google/s2a-go/internal/proto/s2a_go_proto"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// sessionTimeout is the timeout for creating a session with the S2A handshaker
|
||||
// service.
|
||||
const sessionTimeout = time.Second * 5
|
||||
|
||||
// s2aTicketSender sends session tickets to the S2A handshaker service.
|
||||
type s2aTicketSender interface {
|
||||
// sendTicketsToS2A sends the given session tickets to the S2A handshaker
|
||||
// service.
|
||||
sendTicketsToS2A(sessionTickets [][]byte, callComplete chan bool)
|
||||
}
|
||||
|
||||
// ticketStream is the stream used to send and receive session information.
|
||||
type ticketStream interface {
|
||||
Send(*s2apb.SessionReq) error
|
||||
Recv() (*s2apb.SessionResp, error)
|
||||
}
|
||||
|
||||
type ticketSender struct {
|
||||
// hsAddr stores the address of the S2A handshaker service.
|
||||
hsAddr string
|
||||
// connectionID is the connection identifier that was created and sent by
|
||||
// S2A at the end of a handshake.
|
||||
connectionID uint64
|
||||
// localIdentity is the local identity that was used by S2A during session
|
||||
// setup and included in the session result.
|
||||
localIdentity *commonpb.Identity
|
||||
// tokenManager manages access tokens for authenticating to S2A.
|
||||
tokenManager tokenmanager.AccessTokenManager
|
||||
// ensureProcessSessionTickets allows users to wait and ensure that all
|
||||
// available session tickets are sent to S2A before a process completes.
|
||||
ensureProcessSessionTickets *sync.WaitGroup
|
||||
}
|
||||
|
||||
// sendTicketsToS2A sends the given sessionTickets to the S2A handshaker
|
||||
// service. This is done asynchronously and writes to the error logs if an error
|
||||
// occurs.
|
||||
func (t *ticketSender) sendTicketsToS2A(sessionTickets [][]byte, callComplete chan bool) {
|
||||
// Note that the goroutine is in the function rather than at the caller
|
||||
// because the fake ticket sender used for testing must run synchronously
|
||||
// so that the session tickets can be accessed from it after the tests have
|
||||
// been run.
|
||||
if t.ensureProcessSessionTickets != nil {
|
||||
t.ensureProcessSessionTickets.Add(1)
|
||||
}
|
||||
go func() {
|
||||
if err := func() error {
|
||||
defer func() {
|
||||
if t.ensureProcessSessionTickets != nil {
|
||||
t.ensureProcessSessionTickets.Done()
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sessionTimeout)
|
||||
defer cancel()
|
||||
// The transportCreds only needs to be set when talking to S2AV2 and also
|
||||
// if mTLS is required.
|
||||
hsConn, err := service.Dial(ctx, t.hsAddr, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := s2apb.NewS2AServiceClient(hsConn)
|
||||
session, err := client.SetUpSession(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := session.CloseSend(); err != nil {
|
||||
grpclog.Error(err)
|
||||
}
|
||||
}()
|
||||
return t.writeTicketsToStream(session, sessionTickets)
|
||||
}(); err != nil {
|
||||
grpclog.Errorf("failed to send resumption tickets to S2A with identity: %v, %v",
|
||||
t.localIdentity, err)
|
||||
}
|
||||
callComplete <- true
|
||||
close(callComplete)
|
||||
}()
|
||||
}
|
||||
|
||||
// writeTicketsToStream writes the given session tickets to the given stream.
|
||||
func (t *ticketSender) writeTicketsToStream(stream ticketStream, sessionTickets [][]byte) error {
|
||||
if err := stream.Send(
|
||||
&s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_ResumptionTicket{
|
||||
ResumptionTicket: &s2apb.ResumptionTicketReq{
|
||||
InBytes: sessionTickets,
|
||||
ConnectionId: t.connectionID,
|
||||
LocalIdentity: t.localIdentity,
|
||||
},
|
||||
},
|
||||
AuthMechanisms: t.getAuthMechanisms(),
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
sessionResp, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionResp.GetStatus().GetCode() != uint32(codes.OK) {
|
||||
return fmt.Errorf("s2a session ticket response had error status: %v, %v",
|
||||
sessionResp.GetStatus().GetCode(), sessionResp.GetStatus().GetDetails())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ticketSender) getAuthMechanisms() []*s2apb.AuthenticationMechanism {
|
||||
if t.tokenManager == nil {
|
||||
return nil
|
||||
}
|
||||
// First handle the special case when no local identity has been provided
|
||||
// by the application. In this case, an AuthenticationMechanism with no local
|
||||
// identity will be sent.
|
||||
if t.localIdentity == nil {
|
||||
token, err := t.tokenManager.DefaultToken()
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for empty local identity: %v", err)
|
||||
return nil
|
||||
}
|
||||
return []*s2apb.AuthenticationMechanism{
|
||||
{
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Next, handle the case where the application (or the S2A) has specified
|
||||
// a local identity.
|
||||
token, err := t.tokenManager.Token(t.localIdentity)
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for local identity %v: %v", t.localIdentity, err)
|
||||
return nil
|
||||
}
|
||||
return []*s2apb.AuthenticationMechanism{
|
||||
{
|
||||
Identity: t.localIdentity,
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
79
vendor/github.com/google/s2a-go/internal/tokenmanager/tokenmanager.go
generated
vendored
Normal file
79
vendor/github.com/google/s2a-go/internal/tokenmanager/tokenmanager.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package tokenmanager provides tokens for authenticating to S2A.
|
||||
package tokenmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
commonpbv1 "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
commonpb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
)
|
||||
|
||||
const (
|
||||
s2aAccessTokenEnvironmentVariable = "S2A_ACCESS_TOKEN"
|
||||
)
|
||||
|
||||
// AccessTokenManager manages tokens for authenticating to S2A.
|
||||
type AccessTokenManager interface {
|
||||
// DefaultToken returns a token that an application with no specified local
|
||||
// identity must use to authenticate to S2A.
|
||||
DefaultToken() (token string, err error)
|
||||
// Token returns a token that an application with local identity equal to
|
||||
// identity must use to authenticate to S2A.
|
||||
Token(identity interface{}) (token string, err error)
|
||||
}
|
||||
|
||||
type singleTokenAccessTokenManager struct {
|
||||
token string
|
||||
}
|
||||
|
||||
// NewSingleTokenAccessTokenManager returns a new AccessTokenManager instance
|
||||
// that will always manage the same token.
|
||||
//
|
||||
// The token to be managed is read from the s2aAccessTokenEnvironmentVariable
|
||||
// environment variable. If this environment variable is not set, then this
|
||||
// function returns an error.
|
||||
func NewSingleTokenAccessTokenManager() (AccessTokenManager, error) {
|
||||
token, variableExists := os.LookupEnv(s2aAccessTokenEnvironmentVariable)
|
||||
if !variableExists {
|
||||
return nil, fmt.Errorf("%s environment variable is not set", s2aAccessTokenEnvironmentVariable)
|
||||
}
|
||||
return &singleTokenAccessTokenManager{token: token}, nil
|
||||
}
|
||||
|
||||
// DefaultToken always returns the token managed by the
|
||||
// singleTokenAccessTokenManager.
|
||||
func (m *singleTokenAccessTokenManager) DefaultToken() (string, error) {
|
||||
return m.token, nil
|
||||
}
|
||||
|
||||
// Token always returns the token managed by the singleTokenAccessTokenManager.
|
||||
func (m *singleTokenAccessTokenManager) Token(identity interface{}) (string, error) {
|
||||
switch v := identity.(type) {
|
||||
case *commonpbv1.Identity:
|
||||
// valid type.
|
||||
case *commonpb.Identity:
|
||||
// valid type.
|
||||
default:
|
||||
return "", fmt.Errorf("Incorrect identity type: %v", v)
|
||||
}
|
||||
return m.token, nil
|
||||
}
|
||||
1
vendor/github.com/google/s2a-go/internal/v2/README.md
generated
vendored
Normal file
1
vendor/github.com/google/s2a-go/internal/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
**This directory has the implementation of the S2Av2's gRPC-Go client libraries**
|
||||
122
vendor/github.com/google/s2a-go/internal/v2/certverifier/certverifier.go
generated
vendored
Normal file
122
vendor/github.com/google/s2a-go/internal/v2/certverifier/certverifier.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package certverifier offloads verifications to S2Av2.
|
||||
package certverifier
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
// VerifyClientCertificateChain builds a SessionReq, sends it to S2Av2 and
|
||||
// receives a SessionResp.
|
||||
func VerifyClientCertificateChain(verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, s2AStream stream.S2AStream) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
// Offload verification to S2Av2.
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Sending request to S2Av2 for client peer cert chain validation.")
|
||||
}
|
||||
if err := s2AStream.Send(&s2av2pb.SessionReq{
|
||||
ReqOneof: &s2av2pb.SessionReq_ValidatePeerCertificateChainReq{
|
||||
ValidatePeerCertificateChainReq: &s2av2pb.ValidatePeerCertificateChainReq{
|
||||
Mode: verificationMode,
|
||||
PeerOneof: &s2av2pb.ValidatePeerCertificateChainReq_ClientPeer_{
|
||||
ClientPeer: &s2av2pb.ValidatePeerCertificateChainReq_ClientPeer{
|
||||
CertificateChain: rawCerts,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
grpclog.Infof("Failed to send request to S2Av2 for client peer cert chain validation.")
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the response from S2Av2.
|
||||
resp, err := s2AStream.Recv()
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to receive client peer cert chain validation response from S2Av2.")
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the response.
|
||||
if (resp.GetStatus() != nil) && (resp.GetStatus().Code != uint32(codes.OK)) {
|
||||
return fmt.Errorf("failed to offload client cert verification to S2A: %d, %v", resp.GetStatus().Code, resp.GetStatus().Details)
|
||||
|
||||
}
|
||||
|
||||
if resp.GetValidatePeerCertificateChainResp().ValidationResult != s2av2pb.ValidatePeerCertificateChainResp_SUCCESS {
|
||||
return fmt.Errorf("client cert verification failed: %v", resp.GetValidatePeerCertificateChainResp().ValidationDetails)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyServerCertificateChain builds a SessionReq, sends it to S2Av2 and
|
||||
// receives a SessionResp.
|
||||
func VerifyServerCertificateChain(hostname string, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, s2AStream stream.S2AStream, serverAuthorizationPolicy []byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
// Offload verification to S2Av2.
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Sending request to S2Av2 for server peer cert chain validation.")
|
||||
}
|
||||
if err := s2AStream.Send(&s2av2pb.SessionReq{
|
||||
ReqOneof: &s2av2pb.SessionReq_ValidatePeerCertificateChainReq{
|
||||
ValidatePeerCertificateChainReq: &s2av2pb.ValidatePeerCertificateChainReq{
|
||||
Mode: verificationMode,
|
||||
PeerOneof: &s2av2pb.ValidatePeerCertificateChainReq_ServerPeer_{
|
||||
ServerPeer: &s2av2pb.ValidatePeerCertificateChainReq_ServerPeer{
|
||||
CertificateChain: rawCerts,
|
||||
ServerHostname: hostname,
|
||||
SerializedUnrestrictedClientPolicy: serverAuthorizationPolicy,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
grpclog.Infof("Failed to send request to S2Av2 for server peer cert chain validation.")
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the response from S2Av2.
|
||||
resp, err := s2AStream.Recv()
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to receive server peer cert chain validation response from S2Av2.")
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the response.
|
||||
if (resp.GetStatus() != nil) && (resp.GetStatus().Code != uint32(codes.OK)) {
|
||||
return fmt.Errorf("failed to offload server cert verification to S2A: %d, %v", resp.GetStatus().Code, resp.GetStatus().Details)
|
||||
}
|
||||
|
||||
if resp.GetValidatePeerCertificateChainResp().ValidationResult != s2av2pb.ValidatePeerCertificateChainResp_SUCCESS {
|
||||
return fmt.Errorf("server cert verification failed: %v", resp.GetValidatePeerCertificateChainResp().ValidationDetails)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
186
vendor/github.com/google/s2a-go/internal/v2/remotesigner/remotesigner.go
generated
vendored
Normal file
186
vendor/github.com/google/s2a-go/internal/v2/remotesigner/remotesigner.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package remotesigner offloads private key operations to S2Av2.
|
||||
package remotesigner
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
// remoteSigner implementes the crypto.Signer interface.
|
||||
type remoteSigner struct {
|
||||
leafCert *x509.Certificate
|
||||
s2AStream stream.S2AStream
|
||||
}
|
||||
|
||||
// New returns an instance of RemoteSigner, an implementation of the
|
||||
// crypto.Signer interface.
|
||||
func New(leafCert *x509.Certificate, s2AStream stream.S2AStream) crypto.Signer {
|
||||
return &remoteSigner{leafCert, s2AStream}
|
||||
}
|
||||
|
||||
func (s *remoteSigner) Public() crypto.PublicKey {
|
||||
return s.leafCert.PublicKey
|
||||
}
|
||||
|
||||
func (s *remoteSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
signatureAlgorithm, err := getSignatureAlgorithm(opts, s.leafCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := getSignReq(signatureAlgorithm, digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Sending request to S2Av2 for signing operation.")
|
||||
}
|
||||
if err := s.s2AStream.Send(&s2av2pb.SessionReq{
|
||||
ReqOneof: &s2av2pb.SessionReq_OffloadPrivateKeyOperationReq{
|
||||
OffloadPrivateKeyOperationReq: req,
|
||||
},
|
||||
}); err != nil {
|
||||
grpclog.Infof("Failed to send request to S2Av2 for signing operation.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.s2AStream.Recv()
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to receive signing operation response from S2Av2.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (resp.GetStatus() != nil) && (resp.GetStatus().Code != uint32(codes.OK)) {
|
||||
return nil, fmt.Errorf("failed to offload signing with private key to S2A: %d, %v", resp.GetStatus().Code, resp.GetStatus().Details)
|
||||
}
|
||||
|
||||
return resp.GetOffloadPrivateKeyOperationResp().GetOutBytes(), nil
|
||||
}
|
||||
|
||||
// getCert returns the leafCert field in s.
|
||||
func (s *remoteSigner) getCert() *x509.Certificate {
|
||||
return s.leafCert
|
||||
}
|
||||
|
||||
// getStream returns the s2AStream field in s.
|
||||
func (s *remoteSigner) getStream() stream.S2AStream {
|
||||
return s.s2AStream
|
||||
}
|
||||
|
||||
func getSignReq(signatureAlgorithm s2av2pb.SignatureAlgorithm, digest []byte) (*s2av2pb.OffloadPrivateKeyOperationReq, error) {
|
||||
if (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA256) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA256) {
|
||||
return &s2av2pb.OffloadPrivateKeyOperationReq{
|
||||
Operation: s2av2pb.OffloadPrivateKeyOperationReq_SIGN,
|
||||
SignatureAlgorithm: signatureAlgorithm,
|
||||
InBytes: &s2av2pb.OffloadPrivateKeyOperationReq_Sha256Digest{
|
||||
Sha256Digest: digest,
|
||||
},
|
||||
}, nil
|
||||
} else if (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA384) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA384) {
|
||||
return &s2av2pb.OffloadPrivateKeyOperationReq{
|
||||
Operation: s2av2pb.OffloadPrivateKeyOperationReq_SIGN,
|
||||
SignatureAlgorithm: signatureAlgorithm,
|
||||
InBytes: &s2av2pb.OffloadPrivateKeyOperationReq_Sha384Digest{
|
||||
Sha384Digest: digest,
|
||||
},
|
||||
}, nil
|
||||
} else if (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA512) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA512) || (signatureAlgorithm == s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ED25519) {
|
||||
return &s2av2pb.OffloadPrivateKeyOperationReq{
|
||||
Operation: s2av2pb.OffloadPrivateKeyOperationReq_SIGN,
|
||||
SignatureAlgorithm: signatureAlgorithm,
|
||||
InBytes: &s2av2pb.OffloadPrivateKeyOperationReq_Sha512Digest{
|
||||
Sha512Digest: digest,
|
||||
},
|
||||
}, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown signature algorithm: %v", signatureAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
// getSignatureAlgorithm returns the signature algorithm that S2A must use when
|
||||
// performing a signing operation that has been offloaded by an application
|
||||
// using the crypto/tls libraries.
|
||||
func getSignatureAlgorithm(opts crypto.SignerOpts, leafCert *x509.Certificate) (s2av2pb.SignatureAlgorithm, error) {
|
||||
if opts == nil || leafCert == nil {
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_UNSPECIFIED, fmt.Errorf("unknown signature algorithm")
|
||||
}
|
||||
switch leafCert.PublicKeyAlgorithm {
|
||||
case x509.RSA:
|
||||
if rsaPSSOpts, ok := opts.(*rsa.PSSOptions); ok {
|
||||
return rsaPSSAlgorithm(rsaPSSOpts)
|
||||
}
|
||||
return rsaPPKCS1Algorithm(opts)
|
||||
case x509.ECDSA:
|
||||
return ecdsaAlgorithm(opts)
|
||||
case x509.Ed25519:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ED25519, nil
|
||||
default:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_UNSPECIFIED, fmt.Errorf("unknown signature algorithm: %q", leafCert.PublicKeyAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func rsaPSSAlgorithm(opts *rsa.PSSOptions) (s2av2pb.SignatureAlgorithm, error) {
|
||||
switch opts.HashFunc() {
|
||||
case crypto.SHA256:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA256, nil
|
||||
case crypto.SHA384:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA384, nil
|
||||
case crypto.SHA512:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PSS_RSAE_SHA512, nil
|
||||
default:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_UNSPECIFIED, fmt.Errorf("unknown signature algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func rsaPPKCS1Algorithm(opts crypto.SignerOpts) (s2av2pb.SignatureAlgorithm, error) {
|
||||
switch opts.HashFunc() {
|
||||
case crypto.SHA256:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA256, nil
|
||||
case crypto.SHA384:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA384, nil
|
||||
case crypto.SHA512:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_RSA_PKCS1_SHA512, nil
|
||||
default:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_UNSPECIFIED, fmt.Errorf("unknown signature algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func ecdsaAlgorithm(opts crypto.SignerOpts) (s2av2pb.SignatureAlgorithm, error) {
|
||||
switch opts.HashFunc() {
|
||||
case crypto.SHA256:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256, nil
|
||||
case crypto.SHA384:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384, nil
|
||||
case crypto.SHA512:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512, nil
|
||||
default:
|
||||
return s2av2pb.SignatureAlgorithm_S2A_SSL_SIGN_UNSPECIFIED, fmt.Errorf("unknown signature algorithm")
|
||||
}
|
||||
}
|
||||
380
vendor/github.com/google/s2a-go/internal/v2/s2av2.go
generated
vendored
Normal file
380
vendor/github.com/google/s2a-go/internal/v2/s2av2.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package v2 provides the S2Av2 transport credentials used by a gRPC
|
||||
// application.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/s2a-go/fallback"
|
||||
"github.com/google/s2a-go/internal/handshaker/service"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
"github.com/google/s2a-go/internal/v2/tlsconfigstore"
|
||||
"github.com/google/s2a-go/retry"
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
const (
|
||||
s2aSecurityProtocol = "tls"
|
||||
defaultS2ATimeout = 6 * time.Second
|
||||
)
|
||||
|
||||
// An environment variable, which sets the timeout enforced on the connection to the S2A service for handshake.
|
||||
const s2aTimeoutEnv = "S2A_TIMEOUT"
|
||||
|
||||
type s2av2TransportCreds struct {
|
||||
info *credentials.ProtocolInfo
|
||||
isClient bool
|
||||
serverName string
|
||||
s2av2Address string
|
||||
transportCreds credentials.TransportCredentials
|
||||
tokenManager *tokenmanager.AccessTokenManager
|
||||
// localIdentity should only be used by the client.
|
||||
localIdentity *commonpb.Identity
|
||||
// localIdentities should only be used by the server.
|
||||
localIdentities []*commonpb.Identity
|
||||
verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode
|
||||
fallbackClientHandshake fallback.ClientHandshake
|
||||
getS2AStream stream.GetS2AStream
|
||||
serverAuthorizationPolicy []byte
|
||||
}
|
||||
|
||||
// NewClientCreds returns a client-side transport credentials object that uses
|
||||
// the S2Av2 to establish a secure connection with a server.
|
||||
func NewClientCreds(s2av2Address string, transportCreds credentials.TransportCredentials, localIdentity *commonpb.Identity, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, fallbackClientHandshakeFunc fallback.ClientHandshake, getS2AStream stream.GetS2AStream, serverAuthorizationPolicy []byte) (credentials.TransportCredentials, error) {
|
||||
// Create an AccessTokenManager instance to use to authenticate to S2Av2.
|
||||
accessTokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
|
||||
creds := &s2av2TransportCreds{
|
||||
info: &credentials.ProtocolInfo{
|
||||
SecurityProtocol: s2aSecurityProtocol,
|
||||
},
|
||||
isClient: true,
|
||||
serverName: "",
|
||||
s2av2Address: s2av2Address,
|
||||
transportCreds: transportCreds,
|
||||
localIdentity: localIdentity,
|
||||
verificationMode: verificationMode,
|
||||
fallbackClientHandshake: fallbackClientHandshakeFunc,
|
||||
getS2AStream: getS2AStream,
|
||||
serverAuthorizationPolicy: serverAuthorizationPolicy,
|
||||
}
|
||||
if err != nil {
|
||||
creds.tokenManager = nil
|
||||
} else {
|
||||
creds.tokenManager = &accessTokenManager
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Info("Created client S2Av2 transport credentials.")
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// NewServerCreds returns a server-side transport credentials object that uses
|
||||
// the S2Av2 to establish a secure connection with a client.
|
||||
func NewServerCreds(s2av2Address string, transportCreds credentials.TransportCredentials, localIdentities []*commonpb.Identity, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, getS2AStream stream.GetS2AStream) (credentials.TransportCredentials, error) {
|
||||
// Create an AccessTokenManager instance to use to authenticate to S2Av2.
|
||||
accessTokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
creds := &s2av2TransportCreds{
|
||||
info: &credentials.ProtocolInfo{
|
||||
SecurityProtocol: s2aSecurityProtocol,
|
||||
},
|
||||
isClient: false,
|
||||
s2av2Address: s2av2Address,
|
||||
transportCreds: transportCreds,
|
||||
localIdentities: localIdentities,
|
||||
verificationMode: verificationMode,
|
||||
getS2AStream: getS2AStream,
|
||||
}
|
||||
if err != nil {
|
||||
creds.tokenManager = nil
|
||||
} else {
|
||||
creds.tokenManager = &accessTokenManager
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Info("Created server S2Av2 transport credentials.")
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
// ClientHandshake performs a client-side mTLS handshake using the S2Av2.
|
||||
func (c *s2av2TransportCreds) ClientHandshake(ctx context.Context, serverAuthority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if !c.isClient {
|
||||
return nil, nil, errors.New("client handshake called using server transport credentials")
|
||||
}
|
||||
// Remove the port from serverAuthority.
|
||||
serverName := removeServerNamePort(serverAuthority)
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, GetS2ATimeout())
|
||||
defer cancel()
|
||||
var s2AStream stream.S2AStream
|
||||
var err error
|
||||
retry.Run(timeoutCtx,
|
||||
func() error {
|
||||
s2AStream, err = createStream(timeoutCtx, c.s2av2Address, c.transportCreds, c.getS2AStream)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to connect to S2Av2: %v", err)
|
||||
if c.fallbackClientHandshake != nil {
|
||||
return c.fallbackClientHandshake(ctx, serverAuthority, rawConn, err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
defer s2AStream.CloseSend()
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Connected to S2Av2.")
|
||||
}
|
||||
var config *tls.Config
|
||||
|
||||
var tokenManager tokenmanager.AccessTokenManager
|
||||
if c.tokenManager == nil {
|
||||
tokenManager = nil
|
||||
} else {
|
||||
tokenManager = *c.tokenManager
|
||||
}
|
||||
|
||||
sn := serverName
|
||||
if c.serverName != "" {
|
||||
sn = c.serverName
|
||||
}
|
||||
retry.Run(timeoutCtx,
|
||||
func() error {
|
||||
config, err = tlsconfigstore.GetTLSConfigurationForClient(sn, s2AStream, tokenManager, c.localIdentity, c.verificationMode, c.serverAuthorizationPolicy)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Info("Failed to get client TLS config from S2Av2: %v", err)
|
||||
if c.fallbackClientHandshake != nil {
|
||||
return c.fallbackClientHandshake(ctx, serverAuthority, rawConn, err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Got client TLS config from S2Av2.")
|
||||
}
|
||||
|
||||
creds := credentials.NewTLS(config)
|
||||
conn, authInfo, err := creds.ClientHandshake(timeoutCtx, serverName, rawConn)
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to do client handshake using S2Av2: %v", err)
|
||||
if c.fallbackClientHandshake != nil {
|
||||
return c.fallbackClientHandshake(ctx, serverAuthority, rawConn, err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
grpclog.Infof("client-side handshake is done using S2Av2 to: %s", serverName)
|
||||
|
||||
return conn, authInfo, err
|
||||
}
|
||||
|
||||
// ServerHandshake performs a server-side mTLS handshake using the S2Av2.
|
||||
func (c *s2av2TransportCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if c.isClient {
|
||||
return nil, nil, errors.New("server handshake called using client transport credentials")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), GetS2ATimeout())
|
||||
defer cancel()
|
||||
var s2AStream stream.S2AStream
|
||||
var err error
|
||||
retry.Run(ctx,
|
||||
func() error {
|
||||
s2AStream, err = createStream(ctx, c.s2av2Address, c.transportCreds, c.getS2AStream)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to connect to S2Av2: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
defer s2AStream.CloseSend()
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Connected to S2Av2.")
|
||||
}
|
||||
|
||||
var tokenManager tokenmanager.AccessTokenManager
|
||||
if c.tokenManager == nil {
|
||||
tokenManager = nil
|
||||
} else {
|
||||
tokenManager = *c.tokenManager
|
||||
}
|
||||
|
||||
var config *tls.Config
|
||||
retry.Run(ctx,
|
||||
func() error {
|
||||
config, err = tlsconfigstore.GetTLSConfigurationForServer(s2AStream, tokenManager, c.localIdentities, c.verificationMode)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to get server TLS config from S2Av2: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Got server TLS config from S2Av2.")
|
||||
}
|
||||
|
||||
creds := credentials.NewTLS(config)
|
||||
conn, authInfo, err := creds.ServerHandshake(rawConn)
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to do server handshake using S2Av2: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, authInfo, err
|
||||
}
|
||||
|
||||
// Info returns protocol info of s2av2TransportCreds.
|
||||
func (c *s2av2TransportCreds) Info() credentials.ProtocolInfo {
|
||||
return *c.info
|
||||
}
|
||||
|
||||
// Clone makes a deep copy of s2av2TransportCreds.
|
||||
func (c *s2av2TransportCreds) Clone() credentials.TransportCredentials {
|
||||
info := *c.info
|
||||
serverName := c.serverName
|
||||
fallbackClientHandshake := c.fallbackClientHandshake
|
||||
|
||||
s2av2Address := c.s2av2Address
|
||||
var tokenManager tokenmanager.AccessTokenManager
|
||||
if c.tokenManager == nil {
|
||||
tokenManager = nil
|
||||
} else {
|
||||
tokenManager = *c.tokenManager
|
||||
}
|
||||
verificationMode := c.verificationMode
|
||||
var localIdentity *commonpb.Identity
|
||||
if c.localIdentity != nil {
|
||||
localIdentity = proto.Clone(c.localIdentity).(*commonpb.Identity)
|
||||
}
|
||||
var localIdentities []*commonpb.Identity
|
||||
if c.localIdentities != nil {
|
||||
localIdentities = make([]*commonpb.Identity, len(c.localIdentities))
|
||||
for i, localIdentity := range c.localIdentities {
|
||||
localIdentities[i] = proto.Clone(localIdentity).(*commonpb.Identity)
|
||||
}
|
||||
}
|
||||
creds := &s2av2TransportCreds{
|
||||
info: &info,
|
||||
isClient: c.isClient,
|
||||
serverName: serverName,
|
||||
fallbackClientHandshake: fallbackClientHandshake,
|
||||
s2av2Address: s2av2Address,
|
||||
localIdentity: localIdentity,
|
||||
localIdentities: localIdentities,
|
||||
verificationMode: verificationMode,
|
||||
}
|
||||
if c.tokenManager == nil {
|
||||
creds.tokenManager = nil
|
||||
} else {
|
||||
creds.tokenManager = &tokenManager
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
// NewClientTLSConfig returns a tls.Config instance that uses S2Av2 to establish a TLS connection as
|
||||
// a client. The tls.Config MUST only be used to establish a single TLS connection.
|
||||
func NewClientTLSConfig(
|
||||
ctx context.Context,
|
||||
s2av2Address string,
|
||||
transportCreds credentials.TransportCredentials,
|
||||
tokenManager tokenmanager.AccessTokenManager,
|
||||
verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode,
|
||||
serverName string,
|
||||
serverAuthorizationPolicy []byte,
|
||||
getStream stream.GetS2AStream) (*tls.Config, error) {
|
||||
s2AStream, err := createStream(ctx, s2av2Address, transportCreds, getStream)
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to connect to S2Av2: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsconfigstore.GetTLSConfigurationForClient(removeServerNamePort(serverName), s2AStream, tokenManager, nil, verificationMode, serverAuthorizationPolicy)
|
||||
}
|
||||
|
||||
// OverrideServerName sets the ServerName in the s2av2TransportCreds protocol
|
||||
// info. The ServerName MUST be a hostname.
|
||||
func (c *s2av2TransportCreds) OverrideServerName(serverNameOverride string) error {
|
||||
serverName := removeServerNamePort(serverNameOverride)
|
||||
c.info.ServerName = serverName
|
||||
c.serverName = serverName
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the trailing port from server name.
|
||||
func removeServerNamePort(serverName string) string {
|
||||
name, _, err := net.SplitHostPort(serverName)
|
||||
if err != nil {
|
||||
name = serverName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type s2AGrpcStream struct {
|
||||
stream s2av2pb.S2AService_SetUpSessionClient
|
||||
}
|
||||
|
||||
func (x s2AGrpcStream) Send(m *s2av2pb.SessionReq) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x s2AGrpcStream) Recv() (*s2av2pb.SessionResp, error) {
|
||||
return x.stream.Recv()
|
||||
}
|
||||
|
||||
func (x s2AGrpcStream) CloseSend() error {
|
||||
return x.stream.CloseSend()
|
||||
}
|
||||
|
||||
func createStream(ctx context.Context, s2av2Address string, transportCreds credentials.TransportCredentials, getS2AStream stream.GetS2AStream) (stream.S2AStream, error) {
|
||||
if getS2AStream != nil {
|
||||
return getS2AStream(ctx, s2av2Address)
|
||||
}
|
||||
// TODO(rmehta19): Consider whether to close the connection to S2Av2.
|
||||
conn, err := service.Dial(ctx, s2av2Address, transportCreds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := s2av2pb.NewS2AServiceClient(conn)
|
||||
gRPCStream, err := client.SetUpSession(ctx, []grpc.CallOption{}...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s2AGrpcStream{
|
||||
stream: gRPCStream,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetS2ATimeout returns the timeout enforced on the connection to the S2A service for handshake.
|
||||
func GetS2ATimeout() time.Duration {
|
||||
timeout, err := time.ParseDuration(os.Getenv(s2aTimeoutEnv))
|
||||
if err != nil {
|
||||
return defaultS2ATimeout
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
403
vendor/github.com/google/s2a-go/internal/v2/tlsconfigstore/tlsconfigstore.go
generated
vendored
Normal file
403
vendor/github.com/google/s2a-go/internal/v2/tlsconfigstore/tlsconfigstore.go
generated
vendored
Normal file
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package tlsconfigstore offloads operations to S2Av2.
|
||||
package tlsconfigstore
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
"github.com/google/s2a-go/internal/v2/certverifier"
|
||||
"github.com/google/s2a-go/internal/v2/remotesigner"
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// HTTP/2
|
||||
h2 = "h2"
|
||||
)
|
||||
|
||||
// GetTLSConfigurationForClient returns a tls.Config instance for use by a client application.
|
||||
func GetTLSConfigurationForClient(serverHostname string, s2AStream stream.S2AStream, tokenManager tokenmanager.AccessTokenManager, localIdentity *commonpb.Identity, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, serverAuthorizationPolicy []byte) (*tls.Config, error) {
|
||||
authMechanisms := getAuthMechanisms(tokenManager, []*commonpb.Identity{localIdentity})
|
||||
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("Sending request to S2Av2 for client TLS config.")
|
||||
}
|
||||
// Send request to S2Av2 for config.
|
||||
if err := s2AStream.Send(&s2av2pb.SessionReq{
|
||||
LocalIdentity: localIdentity,
|
||||
AuthenticationMechanisms: authMechanisms,
|
||||
ReqOneof: &s2av2pb.SessionReq_GetTlsConfigurationReq{
|
||||
GetTlsConfigurationReq: &s2av2pb.GetTlsConfigurationReq{
|
||||
ConnectionSide: commonpb.ConnectionSide_CONNECTION_SIDE_CLIENT,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
grpclog.Infof("Failed to send request to S2Av2 for client TLS config")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the response containing config from S2Av2.
|
||||
resp, err := s2AStream.Recv()
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to receive client TLS config response from S2Av2.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(rmehta19): Add unit test for this if statement.
|
||||
if (resp.GetStatus() != nil) && (resp.GetStatus().Code != uint32(codes.OK)) {
|
||||
return nil, fmt.Errorf("failed to get TLS configuration from S2A: %d, %v", resp.GetStatus().Code, resp.GetStatus().Details)
|
||||
}
|
||||
|
||||
// Extract TLS configuration from SessionResp.
|
||||
tlsConfig := resp.GetGetTlsConfigurationResp().GetClientTlsConfiguration()
|
||||
|
||||
var cert tls.Certificate
|
||||
for i, v := range tlsConfig.CertificateChain {
|
||||
// Populate Certificates field.
|
||||
block, _ := pem.Decode([]byte(v))
|
||||
if block == nil {
|
||||
return nil, errors.New("certificate in CertificateChain obtained from S2Av2 is empty")
|
||||
}
|
||||
x509Cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert.Certificate = append(cert.Certificate, x509Cert.Raw)
|
||||
if i == 0 {
|
||||
cert.Leaf = x509Cert
|
||||
}
|
||||
}
|
||||
|
||||
if len(tlsConfig.CertificateChain) > 0 {
|
||||
cert.PrivateKey = remotesigner.New(cert.Leaf, s2AStream)
|
||||
if cert.PrivateKey == nil {
|
||||
return nil, errors.New("failed to retrieve Private Key from Remote Signer Library")
|
||||
}
|
||||
}
|
||||
|
||||
minVersion, maxVersion, err := getTLSMinMaxVersionsClient(tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create mTLS credentials for client.
|
||||
config := &tls.Config{
|
||||
VerifyPeerCertificate: certverifier.VerifyServerCertificateChain(serverHostname, verificationMode, s2AStream, serverAuthorizationPolicy),
|
||||
ServerName: serverHostname,
|
||||
InsecureSkipVerify: true, // NOLINT
|
||||
ClientSessionCache: nil,
|
||||
SessionTicketsDisabled: true,
|
||||
MinVersion: minVersion,
|
||||
MaxVersion: maxVersion,
|
||||
NextProtos: []string{h2},
|
||||
}
|
||||
if len(tlsConfig.CertificateChain) > 0 {
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetTLSConfigurationForServer returns a tls.Config instance for use by a server application.
|
||||
func GetTLSConfigurationForServer(s2AStream stream.S2AStream, tokenManager tokenmanager.AccessTokenManager, localIdentities []*commonpb.Identity, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode) (*tls.Config, error) {
|
||||
return &tls.Config{
|
||||
GetConfigForClient: ClientConfig(tokenManager, localIdentities, verificationMode, s2AStream),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClientConfig builds a TLS config for a server to establish a secure
|
||||
// connection with a client, based on SNI communicated during ClientHello.
|
||||
// Ensures that server presents the correct certificate to establish a TLS
|
||||
// connection.
|
||||
func ClientConfig(tokenManager tokenmanager.AccessTokenManager, localIdentities []*commonpb.Identity, verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode, s2AStream stream.S2AStream) func(chi *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
return func(chi *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
tlsConfig, err := getServerConfigFromS2Av2(tokenManager, localIdentities, chi.ServerName, s2AStream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cert tls.Certificate
|
||||
for i, v := range tlsConfig.CertificateChain {
|
||||
// Populate Certificates field.
|
||||
block, _ := pem.Decode([]byte(v))
|
||||
if block == nil {
|
||||
return nil, errors.New("certificate in CertificateChain obtained from S2Av2 is empty")
|
||||
}
|
||||
x509Cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert.Certificate = append(cert.Certificate, x509Cert.Raw)
|
||||
if i == 0 {
|
||||
cert.Leaf = x509Cert
|
||||
}
|
||||
}
|
||||
|
||||
cert.PrivateKey = remotesigner.New(cert.Leaf, s2AStream)
|
||||
if cert.PrivateKey == nil {
|
||||
return nil, errors.New("failed to retrieve Private Key from Remote Signer Library")
|
||||
}
|
||||
|
||||
minVersion, maxVersion, err := getTLSMinMaxVersionsServer(tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientAuth := getTLSClientAuthType(tlsConfig)
|
||||
|
||||
var cipherSuites []uint16
|
||||
cipherSuites = getCipherSuites(tlsConfig.Ciphersuites)
|
||||
|
||||
// Create mTLS credentials for server.
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
VerifyPeerCertificate: certverifier.VerifyClientCertificateChain(verificationMode, s2AStream),
|
||||
ClientAuth: clientAuth,
|
||||
CipherSuites: cipherSuites,
|
||||
SessionTicketsDisabled: true,
|
||||
MinVersion: minVersion,
|
||||
MaxVersion: maxVersion,
|
||||
NextProtos: []string{h2},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getCipherSuites(tlsConfigCipherSuites []commonpb.Ciphersuite) []uint16 {
|
||||
var tlsGoCipherSuites []uint16
|
||||
for _, v := range tlsConfigCipherSuites {
|
||||
s := getTLSCipherSuite(v)
|
||||
if s != 0xffff {
|
||||
tlsGoCipherSuites = append(tlsGoCipherSuites, s)
|
||||
}
|
||||
}
|
||||
return tlsGoCipherSuites
|
||||
}
|
||||
|
||||
func getTLSCipherSuite(tlsCipherSuite commonpb.Ciphersuite) uint16 {
|
||||
switch tlsCipherSuite {
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
||||
return tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
|
||||
return tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
|
||||
return tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
|
||||
return tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
|
||||
return tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
case commonpb.Ciphersuite_CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
|
||||
return tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
default:
|
||||
return 0xffff
|
||||
}
|
||||
}
|
||||
|
||||
func getServerConfigFromS2Av2(tokenManager tokenmanager.AccessTokenManager, localIdentities []*commonpb.Identity, sni string, s2AStream stream.S2AStream) (*s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration, error) {
|
||||
authMechanisms := getAuthMechanisms(tokenManager, localIdentities)
|
||||
var locID *commonpb.Identity
|
||||
if localIdentities != nil {
|
||||
locID = localIdentities[0]
|
||||
}
|
||||
|
||||
if err := s2AStream.Send(&s2av2pb.SessionReq{
|
||||
LocalIdentity: locID,
|
||||
AuthenticationMechanisms: authMechanisms,
|
||||
ReqOneof: &s2av2pb.SessionReq_GetTlsConfigurationReq{
|
||||
GetTlsConfigurationReq: &s2av2pb.GetTlsConfigurationReq{
|
||||
ConnectionSide: commonpb.ConnectionSide_CONNECTION_SIDE_SERVER,
|
||||
Sni: sni,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s2AStream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(rmehta19): Add unit test for this if statement.
|
||||
if (resp.GetStatus() != nil) && (resp.GetStatus().Code != uint32(codes.OK)) {
|
||||
return nil, fmt.Errorf("failed to get TLS configuration from S2A: %d, %v", resp.GetStatus().Code, resp.GetStatus().Details)
|
||||
}
|
||||
|
||||
return resp.GetGetTlsConfigurationResp().GetServerTlsConfiguration(), nil
|
||||
}
|
||||
|
||||
func getTLSClientAuthType(tlsConfig *s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration) tls.ClientAuthType {
|
||||
var clientAuth tls.ClientAuthType
|
||||
switch x := tlsConfig.RequestClientCertificate; x {
|
||||
case s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration_DONT_REQUEST_CLIENT_CERTIFICATE:
|
||||
clientAuth = tls.NoClientCert
|
||||
case s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY:
|
||||
clientAuth = tls.RequestClientCert
|
||||
case s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY:
|
||||
// This case actually maps to tls.VerifyClientCertIfGiven. However this
|
||||
// mapping triggers normal verification, followed by custom verification,
|
||||
// specified in VerifyPeerCertificate. To bypass normal verification, and
|
||||
// only do custom verification we set clientAuth to RequireAnyClientCert or
|
||||
// RequestClientCert. See https://github.com/google/s2a-go/pull/43 for full
|
||||
// discussion.
|
||||
clientAuth = tls.RequireAnyClientCert
|
||||
case s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY:
|
||||
clientAuth = tls.RequireAnyClientCert
|
||||
case s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY:
|
||||
// This case actually maps to tls.RequireAndVerifyClientCert. However this
|
||||
// mapping triggers normal verification, followed by custom verification,
|
||||
// specified in VerifyPeerCertificate. To bypass normal verification, and
|
||||
// only do custom verification we set clientAuth to RequireAnyClientCert or
|
||||
// RequestClientCert. See https://github.com/google/s2a-go/pull/43 for full
|
||||
// discussion.
|
||||
clientAuth = tls.RequireAnyClientCert
|
||||
default:
|
||||
clientAuth = tls.RequireAnyClientCert
|
||||
}
|
||||
return clientAuth
|
||||
}
|
||||
|
||||
func getAuthMechanisms(tokenManager tokenmanager.AccessTokenManager, localIdentities []*commonpb.Identity) []*s2av2pb.AuthenticationMechanism {
|
||||
if tokenManager == nil {
|
||||
return nil
|
||||
}
|
||||
if len(localIdentities) == 0 {
|
||||
token, err := tokenManager.DefaultToken()
|
||||
if err != nil {
|
||||
grpclog.Infof("Unable to get token for empty local identity: %v", err)
|
||||
return nil
|
||||
}
|
||||
return []*s2av2pb.AuthenticationMechanism{
|
||||
{
|
||||
MechanismOneof: &s2av2pb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
var authMechanisms []*s2av2pb.AuthenticationMechanism
|
||||
for _, localIdentity := range localIdentities {
|
||||
if localIdentity == nil {
|
||||
token, err := tokenManager.DefaultToken()
|
||||
if err != nil {
|
||||
grpclog.Infof("Unable to get default token for local identity %v: %v", localIdentity, err)
|
||||
continue
|
||||
}
|
||||
authMechanisms = append(authMechanisms, &s2av2pb.AuthenticationMechanism{
|
||||
Identity: localIdentity,
|
||||
MechanismOneof: &s2av2pb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
token, err := tokenManager.Token(localIdentity)
|
||||
if err != nil {
|
||||
grpclog.Infof("Unable to get token for local identity %v: %v", localIdentity, err)
|
||||
continue
|
||||
}
|
||||
authMechanisms = append(authMechanisms, &s2av2pb.AuthenticationMechanism{
|
||||
Identity: localIdentity,
|
||||
MechanismOneof: &s2av2pb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return authMechanisms
|
||||
}
|
||||
|
||||
// TODO(rmehta19): refactor switch statements into a helper function.
|
||||
func getTLSMinMaxVersionsClient(tlsConfig *s2av2pb.GetTlsConfigurationResp_ClientTlsConfiguration) (uint16, uint16, error) {
|
||||
// Map S2Av2 TLSVersion to consts defined in tls package.
|
||||
var minVersion uint16
|
||||
var maxVersion uint16
|
||||
switch x := tlsConfig.MinTlsVersion; x {
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_0:
|
||||
minVersion = tls.VersionTLS10
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_1:
|
||||
minVersion = tls.VersionTLS11
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_2:
|
||||
minVersion = tls.VersionTLS12
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_3:
|
||||
minVersion = tls.VersionTLS13
|
||||
default:
|
||||
return minVersion, maxVersion, fmt.Errorf("S2Av2 provided invalid MinTlsVersion: %v", x)
|
||||
}
|
||||
|
||||
switch x := tlsConfig.MaxTlsVersion; x {
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_0:
|
||||
maxVersion = tls.VersionTLS10
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_1:
|
||||
maxVersion = tls.VersionTLS11
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_2:
|
||||
maxVersion = tls.VersionTLS12
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_3:
|
||||
maxVersion = tls.VersionTLS13
|
||||
default:
|
||||
return minVersion, maxVersion, fmt.Errorf("S2Av2 provided invalid MaxTlsVersion: %v", x)
|
||||
}
|
||||
if minVersion > maxVersion {
|
||||
return minVersion, maxVersion, errors.New("S2Av2 provided minVersion > maxVersion")
|
||||
}
|
||||
return minVersion, maxVersion, nil
|
||||
}
|
||||
|
||||
func getTLSMinMaxVersionsServer(tlsConfig *s2av2pb.GetTlsConfigurationResp_ServerTlsConfiguration) (uint16, uint16, error) {
|
||||
// Map S2Av2 TLSVersion to consts defined in tls package.
|
||||
var minVersion uint16
|
||||
var maxVersion uint16
|
||||
switch x := tlsConfig.MinTlsVersion; x {
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_0:
|
||||
minVersion = tls.VersionTLS10
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_1:
|
||||
minVersion = tls.VersionTLS11
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_2:
|
||||
minVersion = tls.VersionTLS12
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_3:
|
||||
minVersion = tls.VersionTLS13
|
||||
default:
|
||||
return minVersion, maxVersion, fmt.Errorf("S2Av2 provided invalid MinTlsVersion: %v", x)
|
||||
}
|
||||
|
||||
switch x := tlsConfig.MaxTlsVersion; x {
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_0:
|
||||
maxVersion = tls.VersionTLS10
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_1:
|
||||
maxVersion = tls.VersionTLS11
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_2:
|
||||
maxVersion = tls.VersionTLS12
|
||||
case commonpb.TLSVersion_TLS_VERSION_1_3:
|
||||
maxVersion = tls.VersionTLS13
|
||||
default:
|
||||
return minVersion, maxVersion, fmt.Errorf("S2Av2 provided invalid MaxTlsVersion: %v", x)
|
||||
}
|
||||
if minVersion > maxVersion {
|
||||
return minVersion, maxVersion, errors.New("S2Av2 provided minVersion > maxVersion")
|
||||
}
|
||||
return minVersion, maxVersion, nil
|
||||
}
|
||||
144
vendor/github.com/google/s2a-go/retry/retry.go
generated
vendored
Normal file
144
vendor/github.com/google/s2a-go/retry/retry.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package retry provides a retry helper for talking to S2A gRPC server.
|
||||
// The implementation is modeled after
|
||||
// https://github.com/googleapis/google-cloud-go/blob/main/compute/metadata/retry.go
|
||||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
maxRetryForLoops = 10
|
||||
)
|
||||
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
// Pause returns a duration, which is used as the backoff wait time
|
||||
// before the next retry.
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Sleep will wait for the specified duration or return on context
|
||||
// expiration.
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewRetryer creates an instance of S2ARetryer using the defaultBackoff
|
||||
// implementation.
|
||||
var NewRetryer = func() *S2ARetryer {
|
||||
return &S2ARetryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
// S2ARetryer implements a retry helper for talking to S2A gRPC server.
|
||||
type S2ARetryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
// Attempts return the number of retries attempted.
|
||||
func (r *S2ARetryer) Attempts() int {
|
||||
return r.attempts
|
||||
}
|
||||
|
||||
// Retry returns a boolean indicating whether retry should be performed
|
||||
// and the backoff duration.
|
||||
func (r *S2ARetryer) Retry(err error) (time.Duration, bool) {
|
||||
if err == nil {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts >= maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
// Run uses S2ARetryer to execute the function passed in, until success or reaching
|
||||
// max number of retry attempts.
|
||||
func Run(ctx context.Context, f func() error) {
|
||||
retryer := NewRetryer()
|
||||
forLoopCnt := 0
|
||||
var err error
|
||||
for {
|
||||
err = f()
|
||||
if bo, shouldRetry := retryer.Retry(err); shouldRetry {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("will attempt retry: %v", err)
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("exit retry loop due to context error: %v", ctx.Err())
|
||||
}
|
||||
break
|
||||
}
|
||||
if errSleep := Sleep(ctx, bo); errSleep != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("exit retry loop due to sleep error: %v", errSleep)
|
||||
}
|
||||
break
|
||||
}
|
||||
// This shouldn't happen, just make sure we are not stuck in the for loops.
|
||||
forLoopCnt++
|
||||
if forLoopCnt > maxRetryForLoops {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("exit the for loop after too many retries")
|
||||
}
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("retry conditions not met, exit the loop")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
448
vendor/github.com/google/s2a-go/s2a.go
generated
vendored
Normal file
448
vendor/github.com/google/s2a-go/s2a.go
generated
vendored
Normal file
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package s2a provides the S2A transport credentials used by a gRPC
|
||||
// application.
|
||||
package s2a
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/s2a-go/fallback"
|
||||
"github.com/google/s2a-go/internal/handshaker"
|
||||
"github.com/google/s2a-go/internal/handshaker/service"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
"github.com/google/s2a-go/internal/v2"
|
||||
"github.com/google/s2a-go/retry"
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
commonpbv1 "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
commonpb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
const (
|
||||
s2aSecurityProtocol = "tls"
|
||||
// defaultTimeout specifies the default server handshake timeout.
|
||||
defaultTimeout = 30.0 * time.Second
|
||||
)
|
||||
|
||||
// s2aTransportCreds are the transport credentials required for establishing
|
||||
// a secure connection using the S2A. They implement the
|
||||
// credentials.TransportCredentials interface.
|
||||
type s2aTransportCreds struct {
|
||||
info *credentials.ProtocolInfo
|
||||
minTLSVersion commonpbv1.TLSVersion
|
||||
maxTLSVersion commonpbv1.TLSVersion
|
||||
// tlsCiphersuites contains the ciphersuites used in the S2A connection.
|
||||
// Note that these are currently unconfigurable.
|
||||
tlsCiphersuites []commonpbv1.Ciphersuite
|
||||
// localIdentity should only be used by the client.
|
||||
localIdentity *commonpbv1.Identity
|
||||
// localIdentities should only be used by the server.
|
||||
localIdentities []*commonpbv1.Identity
|
||||
// targetIdentities should only be used by the client.
|
||||
targetIdentities []*commonpbv1.Identity
|
||||
isClient bool
|
||||
s2aAddr string
|
||||
ensureProcessSessionTickets *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewClientCreds returns a client-side transport credentials object that uses
|
||||
// the S2A to establish a secure connection with a server.
|
||||
func NewClientCreds(opts *ClientOptions) (credentials.TransportCredentials, error) {
|
||||
if opts == nil {
|
||||
return nil, errors.New("nil client options")
|
||||
}
|
||||
var targetIdentities []*commonpbv1.Identity
|
||||
for _, targetIdentity := range opts.TargetIdentities {
|
||||
protoTargetIdentity, err := toProtoIdentity(targetIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetIdentities = append(targetIdentities, protoTargetIdentity)
|
||||
}
|
||||
localIdentity, err := toProtoIdentity(opts.LocalIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.EnableLegacyMode {
|
||||
return &s2aTransportCreds{
|
||||
info: &credentials.ProtocolInfo{
|
||||
SecurityProtocol: s2aSecurityProtocol,
|
||||
},
|
||||
minTLSVersion: commonpbv1.TLSVersion_TLS1_3,
|
||||
maxTLSVersion: commonpbv1.TLSVersion_TLS1_3,
|
||||
tlsCiphersuites: []commonpbv1.Ciphersuite{
|
||||
commonpbv1.Ciphersuite_AES_128_GCM_SHA256,
|
||||
commonpbv1.Ciphersuite_AES_256_GCM_SHA384,
|
||||
commonpbv1.Ciphersuite_CHACHA20_POLY1305_SHA256,
|
||||
},
|
||||
localIdentity: localIdentity,
|
||||
targetIdentities: targetIdentities,
|
||||
isClient: true,
|
||||
s2aAddr: opts.S2AAddress,
|
||||
ensureProcessSessionTickets: opts.EnsureProcessSessionTickets,
|
||||
}, nil
|
||||
}
|
||||
verificationMode := getVerificationMode(opts.VerificationMode)
|
||||
var fallbackFunc fallback.ClientHandshake
|
||||
if opts.FallbackOpts != nil && opts.FallbackOpts.FallbackClientHandshakeFunc != nil {
|
||||
fallbackFunc = opts.FallbackOpts.FallbackClientHandshakeFunc
|
||||
}
|
||||
v2LocalIdentity, err := toV2ProtoIdentity(opts.LocalIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v2.NewClientCreds(opts.S2AAddress, opts.TransportCreds, v2LocalIdentity, verificationMode, fallbackFunc, opts.getS2AStream, opts.serverAuthorizationPolicy)
|
||||
}
|
||||
|
||||
// NewServerCreds returns a server-side transport credentials object that uses
|
||||
// the S2A to establish a secure connection with a client.
|
||||
func NewServerCreds(opts *ServerOptions) (credentials.TransportCredentials, error) {
|
||||
if opts == nil {
|
||||
return nil, errors.New("nil server options")
|
||||
}
|
||||
var localIdentities []*commonpbv1.Identity
|
||||
for _, localIdentity := range opts.LocalIdentities {
|
||||
protoLocalIdentity, err := toProtoIdentity(localIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localIdentities = append(localIdentities, protoLocalIdentity)
|
||||
}
|
||||
if opts.EnableLegacyMode {
|
||||
return &s2aTransportCreds{
|
||||
info: &credentials.ProtocolInfo{
|
||||
SecurityProtocol: s2aSecurityProtocol,
|
||||
},
|
||||
minTLSVersion: commonpbv1.TLSVersion_TLS1_3,
|
||||
maxTLSVersion: commonpbv1.TLSVersion_TLS1_3,
|
||||
tlsCiphersuites: []commonpbv1.Ciphersuite{
|
||||
commonpbv1.Ciphersuite_AES_128_GCM_SHA256,
|
||||
commonpbv1.Ciphersuite_AES_256_GCM_SHA384,
|
||||
commonpbv1.Ciphersuite_CHACHA20_POLY1305_SHA256,
|
||||
},
|
||||
localIdentities: localIdentities,
|
||||
isClient: false,
|
||||
s2aAddr: opts.S2AAddress,
|
||||
}, nil
|
||||
}
|
||||
verificationMode := getVerificationMode(opts.VerificationMode)
|
||||
var v2LocalIdentities []*commonpb.Identity
|
||||
for _, localIdentity := range opts.LocalIdentities {
|
||||
protoLocalIdentity, err := toV2ProtoIdentity(localIdentity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v2LocalIdentities = append(v2LocalIdentities, protoLocalIdentity)
|
||||
}
|
||||
return v2.NewServerCreds(opts.S2AAddress, opts.TransportCreds, v2LocalIdentities, verificationMode, opts.getS2AStream)
|
||||
}
|
||||
|
||||
// ClientHandshake initiates a client-side TLS handshake using the S2A.
|
||||
func (c *s2aTransportCreds) ClientHandshake(ctx context.Context, serverAuthority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if !c.isClient {
|
||||
return nil, nil, errors.New("client handshake called using server transport credentials")
|
||||
}
|
||||
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Connect to the S2A.
|
||||
hsConn, err := service.Dial(ctx, c.s2aAddr, nil)
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to connect to S2A: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
opts := &handshaker.ClientHandshakerOptions{
|
||||
MinTLSVersion: c.minTLSVersion,
|
||||
MaxTLSVersion: c.maxTLSVersion,
|
||||
TLSCiphersuites: c.tlsCiphersuites,
|
||||
TargetIdentities: c.targetIdentities,
|
||||
LocalIdentity: c.localIdentity,
|
||||
TargetName: serverAuthority,
|
||||
EnsureProcessSessionTickets: c.ensureProcessSessionTickets,
|
||||
}
|
||||
chs, err := handshaker.NewClientHandshaker(ctx, hsConn, rawConn, c.s2aAddr, opts)
|
||||
if err != nil {
|
||||
grpclog.Infof("Call to handshaker.NewClientHandshaker failed: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if closeErr := chs.Close(); closeErr != nil {
|
||||
grpclog.Infof("Close failed unexpectedly: %v", err)
|
||||
err = fmt.Errorf("%v: close unexpectedly failed: %v", err, closeErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
secConn, authInfo, err := chs.ClientHandshake(context.Background())
|
||||
if err != nil {
|
||||
grpclog.Infof("Handshake failed: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return secConn, authInfo, nil
|
||||
}
|
||||
|
||||
// ServerHandshake initiates a server-side TLS handshake using the S2A.
|
||||
func (c *s2aTransportCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if c.isClient {
|
||||
return nil, nil, errors.New("server handshake called using client transport credentials")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Connect to the S2A.
|
||||
hsConn, err := service.Dial(ctx, c.s2aAddr, nil)
|
||||
if err != nil {
|
||||
grpclog.Infof("Failed to connect to S2A: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
opts := &handshaker.ServerHandshakerOptions{
|
||||
MinTLSVersion: c.minTLSVersion,
|
||||
MaxTLSVersion: c.maxTLSVersion,
|
||||
TLSCiphersuites: c.tlsCiphersuites,
|
||||
LocalIdentities: c.localIdentities,
|
||||
}
|
||||
shs, err := handshaker.NewServerHandshaker(ctx, hsConn, rawConn, c.s2aAddr, opts)
|
||||
if err != nil {
|
||||
grpclog.Infof("Call to handshaker.NewServerHandshaker failed: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if closeErr := shs.Close(); closeErr != nil {
|
||||
grpclog.Infof("Close failed unexpectedly: %v", err)
|
||||
err = fmt.Errorf("%v: close unexpectedly failed: %v", err, closeErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
secConn, authInfo, err := shs.ServerHandshake(context.Background())
|
||||
if err != nil {
|
||||
grpclog.Infof("Handshake failed: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return secConn, authInfo, nil
|
||||
}
|
||||
|
||||
func (c *s2aTransportCreds) Info() credentials.ProtocolInfo {
|
||||
return *c.info
|
||||
}
|
||||
|
||||
func (c *s2aTransportCreds) Clone() credentials.TransportCredentials {
|
||||
info := *c.info
|
||||
var localIdentity *commonpbv1.Identity
|
||||
if c.localIdentity != nil {
|
||||
localIdentity = proto.Clone(c.localIdentity).(*commonpbv1.Identity)
|
||||
}
|
||||
var localIdentities []*commonpbv1.Identity
|
||||
if c.localIdentities != nil {
|
||||
localIdentities = make([]*commonpbv1.Identity, len(c.localIdentities))
|
||||
for i, localIdentity := range c.localIdentities {
|
||||
localIdentities[i] = proto.Clone(localIdentity).(*commonpbv1.Identity)
|
||||
}
|
||||
}
|
||||
var targetIdentities []*commonpbv1.Identity
|
||||
if c.targetIdentities != nil {
|
||||
targetIdentities = make([]*commonpbv1.Identity, len(c.targetIdentities))
|
||||
for i, targetIdentity := range c.targetIdentities {
|
||||
targetIdentities[i] = proto.Clone(targetIdentity).(*commonpbv1.Identity)
|
||||
}
|
||||
}
|
||||
return &s2aTransportCreds{
|
||||
info: &info,
|
||||
minTLSVersion: c.minTLSVersion,
|
||||
maxTLSVersion: c.maxTLSVersion,
|
||||
tlsCiphersuites: c.tlsCiphersuites,
|
||||
localIdentity: localIdentity,
|
||||
localIdentities: localIdentities,
|
||||
targetIdentities: targetIdentities,
|
||||
isClient: c.isClient,
|
||||
s2aAddr: c.s2aAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *s2aTransportCreds) OverrideServerName(serverNameOverride string) error {
|
||||
c.info.ServerName = serverNameOverride
|
||||
return nil
|
||||
}
|
||||
|
||||
// TLSClientConfigOptions specifies parameters for creating client TLS config.
|
||||
type TLSClientConfigOptions struct {
|
||||
// ServerName is required by s2a as the expected name when verifying the hostname found in server's certificate.
|
||||
// tlsConfig, _ := factory.Build(ctx, &s2a.TLSClientConfigOptions{
|
||||
// ServerName: "example.com",
|
||||
// })
|
||||
ServerName string
|
||||
}
|
||||
|
||||
// TLSClientConfigFactory defines the interface for a client TLS config factory.
|
||||
type TLSClientConfigFactory interface {
|
||||
Build(ctx context.Context, opts *TLSClientConfigOptions) (*tls.Config, error)
|
||||
}
|
||||
|
||||
// NewTLSClientConfigFactory returns an instance of s2aTLSClientConfigFactory.
|
||||
func NewTLSClientConfigFactory(opts *ClientOptions) (TLSClientConfigFactory, error) {
|
||||
if opts == nil {
|
||||
return nil, fmt.Errorf("opts must be non-nil")
|
||||
}
|
||||
if opts.EnableLegacyMode {
|
||||
return nil, fmt.Errorf("NewTLSClientConfigFactory only supports S2Av2")
|
||||
}
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
// The only possible error is: access token not set in the environment,
|
||||
// which is okay in environments other than serverless.
|
||||
grpclog.Infof("Access token manager not initialized: %v", err)
|
||||
return &s2aTLSClientConfigFactory{
|
||||
s2av2Address: opts.S2AAddress,
|
||||
transportCreds: opts.TransportCreds,
|
||||
tokenManager: nil,
|
||||
verificationMode: getVerificationMode(opts.VerificationMode),
|
||||
serverAuthorizationPolicy: opts.serverAuthorizationPolicy,
|
||||
getStream: opts.getS2AStream,
|
||||
}, nil
|
||||
}
|
||||
return &s2aTLSClientConfigFactory{
|
||||
s2av2Address: opts.S2AAddress,
|
||||
transportCreds: opts.TransportCreds,
|
||||
tokenManager: tokenManager,
|
||||
verificationMode: getVerificationMode(opts.VerificationMode),
|
||||
serverAuthorizationPolicy: opts.serverAuthorizationPolicy,
|
||||
getStream: opts.getS2AStream,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type s2aTLSClientConfigFactory struct {
|
||||
s2av2Address string
|
||||
transportCreds credentials.TransportCredentials
|
||||
tokenManager tokenmanager.AccessTokenManager
|
||||
verificationMode s2av2pb.ValidatePeerCertificateChainReq_VerificationMode
|
||||
serverAuthorizationPolicy []byte
|
||||
getStream stream.GetS2AStream
|
||||
}
|
||||
|
||||
func (f *s2aTLSClientConfigFactory) Build(
|
||||
ctx context.Context, opts *TLSClientConfigOptions) (*tls.Config, error) {
|
||||
serverName := ""
|
||||
if opts != nil && opts.ServerName != "" {
|
||||
serverName = opts.ServerName
|
||||
}
|
||||
return v2.NewClientTLSConfig(ctx, f.s2av2Address, f.transportCreds, f.tokenManager, f.verificationMode, serverName, f.serverAuthorizationPolicy, f.getStream)
|
||||
}
|
||||
|
||||
func getVerificationMode(verificationMode VerificationModeType) s2av2pb.ValidatePeerCertificateChainReq_VerificationMode {
|
||||
switch verificationMode {
|
||||
case ConnectToGoogle:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_CONNECT_TO_GOOGLE
|
||||
case Spiffe:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_SPIFFE
|
||||
case ReservedCustomVerificationMode3:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_RESERVED_CUSTOM_VERIFICATION_MODE_3
|
||||
case ReservedCustomVerificationMode4:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_RESERVED_CUSTOM_VERIFICATION_MODE_4
|
||||
case ReservedCustomVerificationMode5:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_RESERVED_CUSTOM_VERIFICATION_MODE_5
|
||||
case ReservedCustomVerificationMode6:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_RESERVED_CUSTOM_VERIFICATION_MODE_6
|
||||
default:
|
||||
return s2av2pb.ValidatePeerCertificateChainReq_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
// NewS2ADialTLSContextFunc returns a dialer which establishes an MTLS connection using S2A.
|
||||
// Example use with http.RoundTripper:
|
||||
//
|
||||
// dialTLSContext := s2a.NewS2aDialTLSContextFunc(&s2a.ClientOptions{
|
||||
// S2AAddress: s2aAddress, // required
|
||||
// })
|
||||
// transport := http.DefaultTransport
|
||||
// transport.DialTLSContext = dialTLSContext
|
||||
func NewS2ADialTLSContextFunc(opts *ClientOptions) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
|
||||
fallback := func(err error) (net.Conn, error) {
|
||||
if opts.FallbackOpts != nil && opts.FallbackOpts.FallbackDialer != nil &&
|
||||
opts.FallbackOpts.FallbackDialer.Dialer != nil && opts.FallbackOpts.FallbackDialer.ServerAddr != "" {
|
||||
fbDialer := opts.FallbackOpts.FallbackDialer
|
||||
grpclog.Infof("fall back to dial: %s", fbDialer.ServerAddr)
|
||||
fbConn, fbErr := fbDialer.Dialer.DialContext(ctx, network, fbDialer.ServerAddr)
|
||||
if fbErr != nil {
|
||||
return nil, fmt.Errorf("error fallback to %s: %v; S2A error: %w", fbDialer.ServerAddr, fbErr, err)
|
||||
}
|
||||
return fbConn, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
factory, err := NewTLSClientConfigFactory(opts)
|
||||
if err != nil {
|
||||
grpclog.Infof("error creating S2A client config factory: %v", err)
|
||||
return fallback(err)
|
||||
}
|
||||
|
||||
serverName, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
serverName = addr
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, v2.GetS2ATimeout())
|
||||
defer cancel()
|
||||
|
||||
var s2aTLSConfig *tls.Config
|
||||
var c net.Conn
|
||||
retry.Run(timeoutCtx,
|
||||
func() error {
|
||||
s2aTLSConfig, err = factory.Build(timeoutCtx, &TLSClientConfigOptions{
|
||||
ServerName: serverName,
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Infof("error building S2A TLS config: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
s2aDialer := &tls.Dialer{
|
||||
Config: s2aTLSConfig,
|
||||
}
|
||||
c, err = s2aDialer.DialContext(timeoutCtx, network, addr)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
grpclog.Infof("error dialing with S2A to %s: %v", addr, err)
|
||||
return fallback(err)
|
||||
}
|
||||
grpclog.Infof("success dialing MTLS to %s with S2A", addr)
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
272
vendor/github.com/google/s2a-go/s2a_options.go
generated
vendored
Normal file
272
vendor/github.com/google/s2a-go/s2a_options.go
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package s2a
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/google/s2a-go/fallback"
|
||||
"github.com/google/s2a-go/stream"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
s2av1pb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
s2apb "github.com/google/s2a-go/internal/proto/v2/common_go_proto"
|
||||
)
|
||||
|
||||
// Identity is the interface for S2A identities.
|
||||
type Identity interface {
|
||||
// Name returns the name of the identity.
|
||||
Name() string
|
||||
Attributes() map[string]string
|
||||
}
|
||||
|
||||
type UnspecifiedID struct {
|
||||
Attr map[string]string
|
||||
}
|
||||
|
||||
func (u *UnspecifiedID) Name() string { return "" }
|
||||
|
||||
func (u *UnspecifiedID) Attributes() map[string]string {
|
||||
return u.Attr
|
||||
}
|
||||
|
||||
type spiffeID struct {
|
||||
spiffeID string
|
||||
}
|
||||
|
||||
func (s *spiffeID) Name() string { return s.spiffeID }
|
||||
|
||||
func (spiffeID) Attributes() map[string]string { return nil }
|
||||
|
||||
// NewSpiffeID creates a SPIFFE ID from id.
|
||||
func NewSpiffeID(id string) Identity { return &spiffeID{spiffeID: id} }
|
||||
|
||||
type hostname struct {
|
||||
hostname string
|
||||
}
|
||||
|
||||
func (h *hostname) Name() string { return h.hostname }
|
||||
|
||||
func (hostname) Attributes() map[string]string { return nil }
|
||||
|
||||
// NewHostname creates a hostname from name.
|
||||
func NewHostname(name string) Identity { return &hostname{hostname: name} }
|
||||
|
||||
type uid struct {
|
||||
uid string
|
||||
}
|
||||
|
||||
func (h *uid) Name() string { return h.uid }
|
||||
|
||||
func (uid) Attributes() map[string]string { return nil }
|
||||
|
||||
// NewUID creates a UID from name.
|
||||
func NewUID(name string) Identity { return &uid{uid: name} }
|
||||
|
||||
// VerificationModeType specifies the mode that S2A must use to verify the peer
|
||||
// certificate chain.
|
||||
type VerificationModeType int
|
||||
|
||||
// Three types of verification modes.
|
||||
const (
|
||||
Unspecified VerificationModeType = iota
|
||||
Spiffe
|
||||
ConnectToGoogle
|
||||
ReservedCustomVerificationMode3
|
||||
ReservedCustomVerificationMode4
|
||||
ReservedCustomVerificationMode5
|
||||
ReservedCustomVerificationMode6
|
||||
)
|
||||
|
||||
// ClientOptions contains the client-side options used to establish a secure
|
||||
// channel using the S2A handshaker service.
|
||||
type ClientOptions struct {
|
||||
// TargetIdentities contains a list of allowed server identities. One of the
|
||||
// target identities should match the peer identity in the handshake
|
||||
// result; otherwise, the handshake fails.
|
||||
TargetIdentities []Identity
|
||||
// LocalIdentity is the local identity of the client application. If none is
|
||||
// provided, then the S2A will choose the default identity, if one exists.
|
||||
LocalIdentity Identity
|
||||
// S2AAddress is the address of the S2A.
|
||||
S2AAddress string
|
||||
// Optional transport credentials.
|
||||
// If set, this will be used for the gRPC connection to the S2A server.
|
||||
TransportCreds credentials.TransportCredentials
|
||||
// EnsureProcessSessionTickets waits for all session tickets to be sent to
|
||||
// S2A before a process completes.
|
||||
//
|
||||
// This functionality is crucial for processes that complete very soon after
|
||||
// using S2A to establish a TLS connection, but it can be ignored for longer
|
||||
// lived processes.
|
||||
//
|
||||
// Usage example:
|
||||
// func main() {
|
||||
// var ensureProcessSessionTickets sync.WaitGroup
|
||||
// clientOpts := &s2a.ClientOptions{
|
||||
// EnsureProcessSessionTickets: &ensureProcessSessionTickets,
|
||||
// // Set other members.
|
||||
// }
|
||||
// creds, _ := s2a.NewClientCreds(clientOpts)
|
||||
// conn, _ := grpc.Dial(serverAddr, grpc.WithTransportCredentials(creds))
|
||||
// defer conn.Close()
|
||||
//
|
||||
// // Make RPC call.
|
||||
//
|
||||
// // The process terminates right after the RPC call ends.
|
||||
// // ensureProcessSessionTickets can be used to ensure resumption
|
||||
// // tickets are fully processed. If the process is long-lived, using
|
||||
// // ensureProcessSessionTickets is not necessary.
|
||||
// ensureProcessSessionTickets.Wait()
|
||||
// }
|
||||
EnsureProcessSessionTickets *sync.WaitGroup
|
||||
// If true, enables the use of legacy S2Av1.
|
||||
EnableLegacyMode bool
|
||||
// VerificationMode specifies the mode that S2A must use to verify the
|
||||
// peer certificate chain.
|
||||
VerificationMode VerificationModeType
|
||||
|
||||
// Optional fallback after dialing with S2A fails.
|
||||
FallbackOpts *FallbackOptions
|
||||
|
||||
// Generates an S2AStream interface for talking to the S2A server.
|
||||
getS2AStream stream.GetS2AStream
|
||||
|
||||
// Serialized user specified policy for server authorization.
|
||||
serverAuthorizationPolicy []byte
|
||||
}
|
||||
|
||||
// FallbackOptions prescribes the fallback logic that should be taken if the application fails to connect with S2A.
|
||||
type FallbackOptions struct {
|
||||
// FallbackClientHandshakeFunc is used to specify fallback behavior when calling s2a.NewClientCreds().
|
||||
// It will be called by ClientHandshake function, after handshake with S2A fails.
|
||||
// s2a.NewClientCreds() ignores the other FallbackDialer field.
|
||||
FallbackClientHandshakeFunc fallback.ClientHandshake
|
||||
|
||||
// FallbackDialer is used to specify fallback behavior when calling s2a.NewS2aDialTLSContextFunc().
|
||||
// It passes in a custom fallback dialer and server address to use after dialing with S2A fails.
|
||||
// s2a.NewS2aDialTLSContextFunc() ignores the other FallbackClientHandshakeFunc field.
|
||||
FallbackDialer *FallbackDialer
|
||||
}
|
||||
|
||||
// FallbackDialer contains a fallback tls.Dialer and a server address to connect to.
|
||||
type FallbackDialer struct {
|
||||
// Dialer specifies a fallback tls.Dialer.
|
||||
Dialer *tls.Dialer
|
||||
// ServerAddr is used by Dialer to establish fallback connection.
|
||||
ServerAddr string
|
||||
}
|
||||
|
||||
// DefaultClientOptions returns the default client options.
|
||||
func DefaultClientOptions(s2aAddress string) *ClientOptions {
|
||||
return &ClientOptions{
|
||||
S2AAddress: s2aAddress,
|
||||
VerificationMode: ConnectToGoogle,
|
||||
}
|
||||
}
|
||||
|
||||
// ServerOptions contains the server-side options used to establish a secure
|
||||
// channel using the S2A handshaker service.
|
||||
type ServerOptions struct {
|
||||
// LocalIdentities is the list of local identities that may be assumed by
|
||||
// the server. If no local identity is specified, then the S2A chooses a
|
||||
// default local identity, if one exists.
|
||||
LocalIdentities []Identity
|
||||
// S2AAddress is the address of the S2A.
|
||||
S2AAddress string
|
||||
// Optional transport credentials.
|
||||
// If set, this will be used for the gRPC connection to the S2A server.
|
||||
TransportCreds credentials.TransportCredentials
|
||||
// If true, enables the use of legacy S2Av1.
|
||||
EnableLegacyMode bool
|
||||
// VerificationMode specifies the mode that S2A must use to verify the
|
||||
// peer certificate chain.
|
||||
VerificationMode VerificationModeType
|
||||
|
||||
// Generates an S2AStream interface for talking to the S2A server.
|
||||
getS2AStream stream.GetS2AStream
|
||||
}
|
||||
|
||||
// DefaultServerOptions returns the default server options.
|
||||
func DefaultServerOptions(s2aAddress string) *ServerOptions {
|
||||
return &ServerOptions{
|
||||
S2AAddress: s2aAddress,
|
||||
VerificationMode: ConnectToGoogle,
|
||||
}
|
||||
}
|
||||
|
||||
func toProtoIdentity(identity Identity) (*s2av1pb.Identity, error) {
|
||||
if identity == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch id := identity.(type) {
|
||||
case *spiffeID:
|
||||
return &s2av1pb.Identity{
|
||||
IdentityOneof: &s2av1pb.Identity_SpiffeId{SpiffeId: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *hostname:
|
||||
return &s2av1pb.Identity{
|
||||
IdentityOneof: &s2av1pb.Identity_Hostname{Hostname: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *uid:
|
||||
return &s2av1pb.Identity{
|
||||
IdentityOneof: &s2av1pb.Identity_Uid{Uid: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *UnspecifiedID:
|
||||
return &s2av1pb.Identity{
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("unrecognized identity type")
|
||||
}
|
||||
}
|
||||
|
||||
func toV2ProtoIdentity(identity Identity) (*s2apb.Identity, error) {
|
||||
if identity == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch id := identity.(type) {
|
||||
case *spiffeID:
|
||||
return &s2apb.Identity{
|
||||
IdentityOneof: &s2apb.Identity_SpiffeId{SpiffeId: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *hostname:
|
||||
return &s2apb.Identity{
|
||||
IdentityOneof: &s2apb.Identity_Hostname{Hostname: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *uid:
|
||||
return &s2apb.Identity{
|
||||
IdentityOneof: &s2apb.Identity_Uid{Uid: id.Name()},
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
case *UnspecifiedID:
|
||||
return &s2apb.Identity{
|
||||
Attributes: id.Attributes(),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("unrecognized identity type")
|
||||
}
|
||||
}
|
||||
79
vendor/github.com/google/s2a-go/s2a_utils.go
generated
vendored
Normal file
79
vendor/github.com/google/s2a-go/s2a_utils.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package s2a
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
// AuthInfo exposes security information from the S2A to the application.
|
||||
type AuthInfo interface {
|
||||
// AuthType returns the authentication type.
|
||||
AuthType() string
|
||||
// ApplicationProtocol returns the application protocol, e.g. "grpc".
|
||||
ApplicationProtocol() string
|
||||
// TLSVersion returns the TLS version negotiated during the handshake.
|
||||
TLSVersion() commonpb.TLSVersion
|
||||
// Ciphersuite returns the ciphersuite negotiated during the handshake.
|
||||
Ciphersuite() commonpb.Ciphersuite
|
||||
// PeerIdentity returns the authenticated identity of the peer.
|
||||
PeerIdentity() *commonpb.Identity
|
||||
// LocalIdentity returns the local identity of the application used during
|
||||
// session setup.
|
||||
LocalIdentity() *commonpb.Identity
|
||||
// PeerCertFingerprint returns the SHA256 hash of the peer certificate used in
|
||||
// the S2A handshake.
|
||||
PeerCertFingerprint() []byte
|
||||
// LocalCertFingerprint returns the SHA256 hash of the local certificate used
|
||||
// in the S2A handshake.
|
||||
LocalCertFingerprint() []byte
|
||||
// IsHandshakeResumed returns true if a cached session was used to resume
|
||||
// the handshake.
|
||||
IsHandshakeResumed() bool
|
||||
// SecurityLevel returns the security level of the connection.
|
||||
SecurityLevel() credentials.SecurityLevel
|
||||
}
|
||||
|
||||
// AuthInfoFromPeer extracts the authinfo.S2AAuthInfo object from the given
|
||||
// peer, if it exists. This API should be used by gRPC clients after
|
||||
// obtaining a peer object using the grpc.Peer() CallOption.
|
||||
func AuthInfoFromPeer(p *peer.Peer) (AuthInfo, error) {
|
||||
s2aAuthInfo, ok := p.AuthInfo.(AuthInfo)
|
||||
if !ok {
|
||||
return nil, errors.New("no S2AAuthInfo found in Peer")
|
||||
}
|
||||
return s2aAuthInfo, nil
|
||||
}
|
||||
|
||||
// AuthInfoFromContext extracts the authinfo.S2AAuthInfo object from the given
|
||||
// context, if it exists. This API should be used by gRPC server RPC handlers
|
||||
// to get information about the peer. On the client-side, use the grpc.Peer()
|
||||
// CallOption and the AuthInfoFromPeer function.
|
||||
func AuthInfoFromContext(ctx context.Context) (AuthInfo, error) {
|
||||
p, ok := peer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("no Peer found in Context")
|
||||
}
|
||||
return AuthInfoFromPeer(p)
|
||||
}
|
||||
39
vendor/github.com/google/s2a-go/stream/s2a_stream.go
generated
vendored
Normal file
39
vendor/github.com/google/s2a-go/stream/s2a_stream.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package stream provides an interface for bidirectional streaming to the S2A server.
|
||||
package stream
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
s2av2pb "github.com/google/s2a-go/internal/proto/v2/s2a_go_proto"
|
||||
)
|
||||
|
||||
// S2AStream defines the operation for communicating with the S2A server over a bidirectional stream.
|
||||
type S2AStream interface {
|
||||
// Send sends the message to the S2A server.
|
||||
Send(*s2av2pb.SessionReq) error
|
||||
// Recv receives the message from the S2A server.
|
||||
Recv() (*s2av2pb.SessionResp, error)
|
||||
// Closes the channel to the S2A server.
|
||||
CloseSend() error
|
||||
}
|
||||
|
||||
// GetS2AStream type is for generating an S2AStream interface for talking to the S2A server.
|
||||
type GetS2AStream func(ctx context.Context, s2av2Address string, opts ...string) (S2AStream, error)
|
||||
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||
|
||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||
|
||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||
|
||||
### Fixes
|
||||
|
||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||
|
||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||
|
||||
## Changelog
|
||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Tips
|
||||
|
||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||
|
||||
Always try to include a test case! If it is not possible or not necessary,
|
||||
please explain why in the pull request description.
|
||||
|
||||
### Releasing
|
||||
|
||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||
to create a release candidate pull request. Once submitted, `release-please`
|
||||
will create a release.
|
||||
|
||||
For tips on how to work with `release-please`, see its documentation.
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# uuid
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
```sh
|
||||
go get github.com/google/uuid
|
||||
```
|
||||
|
||||
###### Documentation
|
||||
[](https://pkg.go.dev/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://pkg.go.dev/github.com/google/uuid
|
||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
|
||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||
Max = UUID{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*uuid = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This removes the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
var t Time
|
||||
switch uuid.Version() {
|
||||
case 6:
|
||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||
t = Time(time)
|
||||
case 7:
|
||||
time := binary.BigEndian.Uint64(uuid[:8])
|
||||
t = Time((time>>16)*10000 + g1582ns100)
|
||||
default: // forward compatible
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
t = Time(time)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
||||
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
||||
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
const randPoolSize = 16 * 16
|
||||
|
||||
var (
|
||||
rander = rand.Reader // random function
|
||||
poolEnabled = false
|
||||
poolMu sync.Mutex
|
||||
poolPos = randPoolSize // protected with poolMu
|
||||
pool [randPoolSize]byte // protected with poolMu
|
||||
)
|
||||
|
||||
type invalidLengthError struct{ len int }
|
||||
|
||||
func (err invalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||
}
|
||||
|
||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||
func IsInvalidLengthError(err error) bool {
|
||||
_, ok := err.(invalidLengthError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
||||
// the standard UUID forms defined in RFC 4122
|
||||
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
||||
// Parse accepts non-standard strings such as the raw hex encoding
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
||||
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
||||
// examined in the latter case. Parse should not be used to validate strings as
|
||||
// it parses non-standard encodings as indicated above.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(s)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(b)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
// It returns an error if the format is invalid, otherwise nil.
|
||||
func Validate(s string) error {
|
||||
switch len(s) {
|
||||
// Standard UUID format
|
||||
case 36:
|
||||
|
||||
// UUID with "urn:uuid:" prefix
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// UUID enclosed in braces
|
||||
case 36 + 2:
|
||||
if s[0] != '{' || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("invalid bracketed UUID format")
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// UUID without hyphens
|
||||
case 32:
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
_, ok := xtob(s[i], s[i+1])
|
||||
if !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return invalidLengthError{len(s)}
|
||||
}
|
||||
|
||||
// Check for standard UUID format
|
||||
if len(s) == 36 {
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
if _, ok := xtob(s[x], s[x+1]); !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
|
||||
// EnableRandPool enables internal randomness pool used for Random
|
||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||
// the random number generator on demand in batches. Enabling the pool
|
||||
// may improve the UUID generation throughput significantly.
|
||||
//
|
||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||
// for security sensitive applications.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func EnableRandPool() {
|
||||
poolEnabled = true
|
||||
}
|
||||
|
||||
// DisableRandPool disables the randomness pool if it was previously
|
||||
// enabled with EnableRandPool.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func DisableRandPool() {
|
||||
poolEnabled = false
|
||||
defer poolMu.Unlock()
|
||||
poolMu.Lock()
|
||||
poolPos = randPoolSize
|
||||
}
|
||||
|
||||
// UUIDs is a slice of UUID types.
|
||||
type UUIDs []UUID
|
||||
|
||||
// Strings returns a string slice containing the string form of each UUID in uuids.
|
||||
func (uuids UUIDs) Strings() []string {
|
||||
var uuidStrs = make([]string, len(uuids))
|
||||
for i, uuid := range uuids {
|
||||
uuidStrs[i] = uuid.String()
|
||||
}
|
||||
return uuidStrs
|
||||
}
|
||||
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewString creates a new random UUID and returns it as a string or panics.
|
||||
// NewString is equivalent to the expression
|
||||
//
|
||||
// uuid.New().String()
|
||||
func NewString() string {
|
||||
return Must(NewRandom()).String()
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
if !poolEnabled {
|
||||
return NewRandomFromReader(rander)
|
||||
}
|
||||
return newRandomFromPool()
|
||||
}
|
||||
|
||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(r, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func newRandomFromPool() (UUID, error) {
|
||||
var uuid UUID
|
||||
poolMu.Lock()
|
||||
if poolPos == randPoolSize {
|
||||
_, err := io.ReadFull(rander, pool[:])
|
||||
if err != nil {
|
||||
poolMu.Unlock()
|
||||
return Nil, err
|
||||
}
|
||||
poolPos = 0
|
||||
}
|
||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||
poolPos += 16
|
||||
poolMu.Unlock()
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
||||
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
||||
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
||||
//
|
||||
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewV6 returns Nil and an error.
|
||||
func NewV6() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_high |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_mid | time_low_and_version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| node (2-5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
||||
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// UUID version 7 features a time-ordered value field derived from the widely
|
||||
// implemented and well known Unix Epoch timestamp source,
|
||||
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
||||
// As well as improved entropy characteristics over versions 1 or 6.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
||||
//
|
||||
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
||||
//
|
||||
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
// On error, NewV7 returns Nil and an error
|
||||
func NewV7() (UUID, error) {
|
||||
uuid, err := NewRandom()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// it use NewRandomFromReader fill random bits.
|
||||
// On error, NewV7FromReader returns Nil and an error.
|
||||
func NewV7FromReader(r io.Reader) (UUID, error) {
|
||||
uuid, err := NewRandomFromReader(r)
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
||||
// uuid[8] already has the right version number (Variant is 10)
|
||||
// see function NewV7 and NewV7FromReader
|
||||
func makeV7(uuid []byte) {
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|var| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
_ = uuid[15] // bounds check
|
||||
|
||||
t, s := getV7Time()
|
||||
|
||||
uuid[0] = byte(t >> 40)
|
||||
uuid[1] = byte(t >> 32)
|
||||
uuid[2] = byte(t >> 24)
|
||||
uuid[3] = byte(t >> 16)
|
||||
uuid[4] = byte(t >> 8)
|
||||
uuid[5] = byte(t)
|
||||
|
||||
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
||||
uuid[7] = byte(s)
|
||||
}
|
||||
|
||||
// lastV7time is the last time we returned stored as:
|
||||
//
|
||||
// 52 bits of time in milliseconds since epoch
|
||||
// 12 bits of (fractional nanoseconds) >> 8
|
||||
var lastV7time int64
|
||||
|
||||
const nanoPerMilli = 1000000
|
||||
|
||||
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
||||
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
||||
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
||||
func getV7Time() (milli, seq int64) {
|
||||
timeMu.Lock()
|
||||
defer timeMu.Unlock()
|
||||
|
||||
nano := timeNow().UnixNano()
|
||||
milli = nano / nanoPerMilli
|
||||
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
||||
seq = (nano - milli*nanoPerMilli) >> 8
|
||||
now := milli<<12 + seq
|
||||
if now <= lastV7time {
|
||||
now = lastV7time + 1
|
||||
milli = now >> 12
|
||||
seq = now & 0xfff
|
||||
}
|
||||
lastV7time = now
|
||||
return milli, seq
|
||||
}
|
||||
Reference in New Issue
Block a user