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 }