github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/value/encryption/age/transformer.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package age
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"strings"
    28  
    29  	"filippo.io/age"
    30  	"filippo.io/age/armor"
    31  
    32  	"github.com/zntrio/harp/v2/pkg/sdk/ioutil"
    33  	"github.com/zntrio/harp/v2/pkg/sdk/value"
    34  	"github.com/zntrio/harp/v2/pkg/sdk/value/encryption"
    35  )
    36  
    37  const (
    38  	agePublicPrefix   = "age-recipients"
    39  	agePrivatePrefix  = "age-identity"
    40  	ageMaxPayloadSize = 25 * 1024 * 1024
    41  )
    42  
    43  func init() {
    44  	encryption.Register(agePublicPrefix, Transformer)
    45  	encryption.Register(agePrivatePrefix, Transformer)
    46  }
    47  
    48  // Transformer returns a fernet encryption transformer.
    49  func Transformer(key string) (value.Transformer, error) {
    50  	switch {
    51  	case strings.HasPrefix(key, "age-recipients:"):
    52  		// Remove the prefix
    53  		key = strings.TrimPrefix(key, "age-recipients:")
    54  
    55  		// Split recipients
    56  		recipientRaw := strings.Split(key, ":")
    57  
    58  		recipients := []age.Recipient{}
    59  		for _, r := range recipientRaw {
    60  			// Check given keys
    61  			k, err := age.ParseX25519Recipient(r)
    62  			if err != nil {
    63  				return nil, fmt.Errorf("age: unable to initialize age transformer, %q is an invalid recipient: %w", r, err)
    64  			}
    65  
    66  			// Add to recipients
    67  			recipients = append(recipients, k)
    68  		}
    69  
    70  		// Return decorator constructor
    71  		return &ageTransformer{
    72  			recipients: recipients,
    73  		}, nil
    74  	case strings.HasPrefix(key, "age-identity:"):
    75  		// Remove the prefix
    76  		key = strings.TrimPrefix(key, "age-identity:")
    77  
    78  		// Split identities
    79  		identityRaw := strings.Split(key, ":")
    80  
    81  		identities := []age.Identity{}
    82  		for _, r := range identityRaw {
    83  			// Check given keys
    84  			k, err := age.ParseX25519Identity(r)
    85  			if err != nil {
    86  				return nil, fmt.Errorf("age: unable to initialize age transformer, %q is an invalid identity: %w", r, err)
    87  			}
    88  
    89  			// Add to identities
    90  			identities = append(identities, k)
    91  		}
    92  
    93  		// Return decorator constructor
    94  		return &ageTransformer{
    95  			identities: identities,
    96  		}, nil
    97  	}
    98  
    99  	// Default to error
   100  	return nil, errors.New("age: prefix not supported")
   101  }
   102  
   103  // -----------------------------------------------------------------------------
   104  
   105  type ageTransformer struct {
   106  	recipients []age.Recipient
   107  	identities []age.Identity
   108  }
   109  
   110  func (d *ageTransformer) To(_ context.Context, input []byte) ([]byte, error) {
   111  	var (
   112  		in  = bytes.NewReader(input)
   113  		buf = &bytes.Buffer{}
   114  	)
   115  
   116  	// Check recipients count
   117  	if len(d.recipients) == 0 {
   118  		return nil, errors.New("no recipients specified")
   119  	}
   120  
   121  	// Amrmor writer
   122  	a := armor.NewWriter(buf)
   123  
   124  	// Encrypt with given recipients
   125  	w, err := age.Encrypt(a, d.recipients...)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	// Copy stream
   131  	if err := ioutil.Copy(ageMaxPayloadSize, w, in); err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	// Close the writer
   136  	if err := w.Close(); err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	// Close armor writer
   141  	if err := a.Close(); err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	// No error
   146  	return buf.Bytes(), nil
   147  }
   148  
   149  func (d *ageTransformer) From(_ context.Context, input []byte) ([]byte, error) {
   150  	var (
   151  		in  io.Reader = bytes.NewReader(input)
   152  		out bytes.Buffer
   153  	)
   154  
   155  	// Check identities count
   156  	if len(d.identities) == 0 {
   157  		return nil, errors.New("no identities specified")
   158  	}
   159  
   160  	// Check armor usage
   161  	rr := bufio.NewReader(in)
   162  	if start, _ := rr.Peek(len(armor.Header)); string(start) == armor.Header {
   163  		in = armor.NewReader(rr)
   164  	} else {
   165  		in = rr
   166  	}
   167  
   168  	// Decrypt with given identities
   169  	w, err := age.Decrypt(in, d.identities...)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	// Copy stream
   175  	if err := ioutil.Copy(ageMaxPayloadSize, &out, w); err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	// No error
   180  	return out.Bytes(), nil
   181  }