pkg/container/codec.go (161 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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. package container import ( "encoding/binary" "errors" "fmt" "io" "strings" "github.com/awnumar/memguard" "google.golang.org/protobuf/proto" containerv1 "github.com/elastic/harp/api/gen/go/harp/container/v1" "github.com/elastic/harp/pkg/container/identity/key" "github.com/elastic/harp/pkg/container/seal" v1 "github.com/elastic/harp/pkg/container/seal/v1" v2 "github.com/elastic/harp/pkg/container/seal/v2" "github.com/elastic/harp/pkg/sdk/types" ) const ( containerSealedContentType = "application/vnd.harp.v1.SealedContainer" containerMagic = uint32(0x53CB3701) containerVersion = uint16(0x0002) ) // Load a reader to extract as a container. func Load(r io.Reader) (*containerv1.Container, error) { // Check parameters if types.IsNil(r) { return nil, fmt.Errorf("unable to process nil reader") } // Read magic var magic uint32 if err := binary.Read(r, binary.BigEndian, &magic); err != nil { return nil, fmt.Errorf("unable to read magic code: %w", err) } // Check magic value if magic != containerMagic { return nil, fmt.Errorf("invalid magic signature") } // Read container version var version uint16 if err := binary.Read(r, binary.BigEndian, &version); err != nil { return nil, fmt.Errorf("unable to read container version: %w", err) } // Check magic value if version != containerVersion { return nil, fmt.Errorf("invalid container version %d", version) } // Drain input reader decoded, err := io.ReadAll(r) if err != nil { return nil, fmt.Errorf("unable to container content") } // Check content length if len(decoded) == 0 { return nil, fmt.Errorf("container is empty") } // Deserialize protobuf payload container := &containerv1.Container{} if err = proto.Unmarshal(decoded, container); err != nil { return nil, fmt.Errorf("unable to decode content as container") } // Check headers if container.Headers == nil { container.Headers = &containerv1.Header{} } // No error return container, nil } // Dump the marshaled container instance to writer. // //nolint:interfacer // Tightly coupled to type func Dump(w io.Writer, c *containerv1.Container) error { // Check parameters if types.IsNil(w) { return fmt.Errorf("unable to process nil writer") } if c == nil { return fmt.Errorf("unable to process nil container") } // Serialize protobuf payload payload, err := proto.Marshal(c) if err != nil { return fmt.Errorf("unable to encode container content: %w", err) } // Write packets if err = binary.Write(w, binary.BigEndian, containerMagic); err != nil { return fmt.Errorf("unable to write container magic: %w", err) } if err = binary.Write(w, binary.BigEndian, containerVersion); err != nil { return fmt.Errorf("unable to write container version: %w", err) } if _, err = w.Write(payload); err != nil { return fmt.Errorf("unable to write container content: %w", err) } // No error return nil } // Unseal a sealed container with the given identity func Unseal(container *containerv1.Container, identity *memguard.LockedBuffer) (*containerv1.Container, error) { // Check parameters if types.IsNil(container) { return nil, fmt.Errorf("unable to process nil container") } if types.IsNil(container.Headers) { return nil, fmt.Errorf("unable to process nil container headers") } if identity == nil { return nil, fmt.Errorf("unable to process without container key") } // Check headers if container.Headers.ContentType != containerSealedContentType { return nil, fmt.Errorf("unable to unseal container") } // Build appropriate unseal strategy processor. var ss seal.Strategy switch container.Headers.SealVersion { case 2: ss = v2.New() default: ss = v1.New() } // Delegate to strategy return ss.Unseal(container, identity) } // IsSealed returns true if the given container is sealed. func IsSealed(container *containerv1.Container) bool { // Check parameters if types.IsNil(container) { return false } if types.IsNil(container.Headers) { return false } // Check headers if container.Headers.ContentType != containerSealedContentType { return false } // Check sealing algorithm if container.Headers.SealVersion == 0 { return false } // Default sealed return true } func Seal(rand io.Reader, container *containerv1.Container, encodedPeersPublicKey ...string) (*containerv1.Container, error) { // Check parameters if types.IsNil(container) { return nil, errors.New("unable to seal a nil container") } if IsSealed(container) { return nil, errors.New("the container is already sealed") } // Validate peer public keys hasV1 := false hasV2 := false for i, pub := range encodedPeersPublicKey { switch { case strings.HasPrefix(pub, "v1.sk."): hasV1 = true case strings.HasPrefix(pub, "v1.ipk."): hasV1 = true // Convert to sealing public key identityPublicKey, err := key.FromString(pub) if err != nil { return nil, fmt.Errorf("unable to convert v1 identity public key '%s': %w", pub, err) } // Replace identity key by sealing key encodedPeersPublicKey[i] = identityPublicKey.SealingKey() case strings.HasPrefix(pub, "v2.sk."): hasV2 = true case strings.HasPrefix(pub, "v2.ipk."): hasV2 = true // Convert to sealing public key identityPublicKey, err := key.FromString(pub) if err != nil { return nil, fmt.Errorf("unable to convert v2 identity public key '%s': %w", pub, err) } // Replace identity key by sealing key encodedPeersPublicKey[i] = identityPublicKey.SealingKey() default: return nil, fmt.Errorf("invalid key '%s'", pub) } } if hasV1 && hasV2 { return nil, errors.New("peer public keys are using mixed versions - use v1 or v2 keys") } // Create sealing strategy instance var ss seal.Strategy switch { case hasV1: ss = v1.New() case hasV2: ss = v2.New() default: return nil, errors.New("unsupported sealing algorithm") } // Delegate to strategy return ss.Seal(rand, container, encodedPeersPublicKey...) }