github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v2/helpers.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 v2 19 20 import ( 21 "bytes" 22 "crypto/aes" 23 "crypto/cipher" 24 "crypto/ecdsa" 25 "crypto/elliptic" 26 "crypto/hmac" 27 cryptorand "crypto/rand" 28 "crypto/sha512" 29 "encoding/binary" 30 "errors" 31 "fmt" 32 "io" 33 34 "github.com/awnumar/memguard" 35 "google.golang.org/protobuf/proto" 36 37 containerv1 "github.com/zntrio/harp/v2/api/gen/go/harp/container/v1" 38 "github.com/zntrio/harp/v2/pkg/sdk/security" 39 40 "golang.org/x/crypto/hkdf" 41 ) 42 43 func pskStretch(key, salt []byte) *[preSharedKeySize]byte { 44 pskh := hmac.New(sha512.New, key) 45 pskh.Write(salt) 46 hashPsk := pskh.Sum(nil) 47 48 psk := &[preSharedKeySize]byte{} 49 copy(psk[:], hashPsk[:preSharedKeySize]) 50 51 return psk 52 } 53 54 func tryRecipientKeys(derivedKey *[32]byte, recipients []*containerv1.Recipient, preSharedKey *[preSharedKeySize]byte) (*[32]byte, error) { 55 // Calculate recipient identifier 56 identifier, err := keyIdentifierFromDerivedKey(derivedKey, preSharedKey) 57 if err != nil { 58 return nil, fmt.Errorf("unable to generate identifier: %w", err) 59 } 60 61 // Find matching recipient 62 for _, r := range recipients { 63 // Check recipient identifiers 64 if !security.SecureCompare(identifier, r.Identifier) { 65 continue 66 } 67 68 // Try to decrypt the secretbox with the derived key. 69 clearText, err := decrypt(r.Key, derivedKey) 70 if err != nil { 71 return nil, fmt.Errorf("invalid recipient encryption key") 72 } 73 74 var payloadKey [32]byte 75 copy(payloadKey[:], clearText) 76 77 // Encryption key found, return no error. 78 return &payloadKey, nil 79 } 80 81 // No recipient found in list. 82 return nil, fmt.Errorf("no recipient found") 83 } 84 85 func prepareSignature(rand io.Reader, encryptionKey *[32]byte) (*ecdsa.PrivateKey, []byte, error) { 86 // Generate ephemeral signing key 87 sigPriv, err := ecdsa.GenerateKey(signatureCurve, cryptorand.Reader) 88 if err != nil { 89 return nil, nil, fmt.Errorf("unable to generate signing keypair") 90 } 91 92 // Compress public key point 93 sigPub := elliptic.MarshalCompressed(sigPriv.Curve, sigPriv.PublicKey.X, sigPriv.PublicKey.Y) 94 95 // Encrypt public signing key 96 encryptedPubSig, err := encrypt(rand, sigPub, encryptionKey) 97 if err != nil { 98 return nil, nil, fmt.Errorf("unable to encrypt public signing key: %w", err) 99 } 100 101 // Cleanup 102 memguard.WipeBytes(sigPub) 103 104 // No error 105 return sigPriv, encryptedPubSig, nil 106 } 107 108 func signContainer(sigPriv *ecdsa.PrivateKey, headers *containerv1.Header, container *containerv1.Container) (content, containerSig []byte, err error) { 109 // Serialize protobuf payload 110 content, err = proto.Marshal(container) 111 if err != nil { 112 return nil, nil, fmt.Errorf("unable to encode container content: %w", err) 113 } 114 115 // Compute header hash 116 headerHash, err := computeHeaderHash(headers) 117 if err != nil { 118 return nil, nil, fmt.Errorf("unable to compute header hash: %w", err) 119 } 120 121 // Compute protected content hash 122 protectedHash := computeProtectedHash(headerHash, content) 123 124 // Sign the protected content 125 r, s, err := ecdsa.Sign(cryptorand.Reader, sigPriv, protectedHash) 126 if err != nil { 127 return nil, nil, fmt.Errorf("unable to sign protected content: %w", err) 128 } 129 130 // Container signature 131 containerSig = append(r.Bytes(), s.Bytes()...) 132 133 // No error 134 return content, containerSig, nil 135 } 136 137 func generatedEncryptionKey(rand io.Reader) (*[32]byte, error) { 138 // Generate payload encryption key 139 var payloadKey [encryptionKeySize]byte 140 if _, err := io.ReadFull(rand, payloadKey[:]); err != nil { 141 return nil, fmt.Errorf("unable to generate payload key for encryption") 142 } 143 144 // No error 145 return &payloadKey, nil 146 } 147 148 func encrypt(rand io.Reader, plaintext []byte, key *[32]byte) ([]byte, error) { 149 // Check cleartext message size. 150 if len(plaintext) > messageLimit { 151 return nil, errors.New("value too large") 152 } 153 154 // Generate random nonce 155 var seed [seedSize]byte 156 if _, err := io.ReadFull(rand, seed[:]); err != nil { 157 return nil, fmt.Errorf("unable to generate random encryption nonce: %w", err) 158 } 159 160 // Derive keys from seed and secret key 161 ek, n2, ak, err := kdf(key, seed[:]) 162 if err != nil { 163 return nil, fmt.Errorf("unable to derive keys from seed: %w", err) 164 } 165 166 // Prepare an AES-256-CTR stream cipher 167 block, err := aes.NewCipher(ek) 168 if err != nil { 169 return nil, fmt.Errorf("unable to prepare block cipher: %w", err) 170 } 171 ciph := cipher.NewCTR(block, n2) 172 173 // Encrypt the payload 174 c := make([]byte, len(plaintext)) 175 ciph.XORKeyStream(c, plaintext) 176 177 // Compute MAC 178 t, err := mac(ak, seed[:], c) 179 if err != nil { 180 return nil, fmt.Errorf("paseto: unable to compute MAC: %w", err) 181 } 182 183 // Serialize final payload 184 // n || c || t 185 body := append([]byte{}, seed[:]...) 186 body = append(body, c...) 187 body = append(body, t...) 188 189 // No error 190 return body, nil 191 } 192 193 func decrypt(ciphertext []byte, key *[32]byte) ([]byte, error) { 194 // Check arguments 195 if len(ciphertext) < nonceSize { 196 return nil, errors.New("ciphered text too short") 197 } 198 199 // Extract components 200 n := ciphertext[:seedSize] 201 t := ciphertext[len(ciphertext)-macSize:] 202 c := ciphertext[seedSize : len(ciphertext)-macSize] 203 204 // Derive keys from seed and secret key 205 ek, n2, ak, err := kdf(key, n) 206 if err != nil { 207 return nil, fmt.Errorf("unable to derive keys from seed: %w", err) 208 } 209 210 // Compute MAC 211 t2, err := mac(ak, n, c) 212 if err != nil { 213 return nil, fmt.Errorf("unable to compute MAC: %w", err) 214 } 215 216 // Time-constant compare MAC 217 if !security.SecureCompare(t, t2) { 218 return nil, errors.New("invalid pre-authentication header") 219 } 220 221 // Prepare an AES-256-CTR stream cipher 222 block, err := aes.NewCipher(ek) 223 if err != nil { 224 return nil, fmt.Errorf("unable to prepare block cipher: %w", err) 225 } 226 ciph := cipher.NewCTR(block, n2) 227 228 // Decrypt the payload 229 m := make([]byte, len(c)) 230 ciph.XORKeyStream(m, c) 231 232 // No error 233 return m, nil 234 } 235 236 func computeHeaderHash(headers *containerv1.Header) ([]byte, error) { 237 // Check arguments 238 if headers == nil { 239 return nil, errors.New("unable process with nil headers") 240 } 241 242 // Prepare signature 243 header, err := proto.Marshal(headers) 244 if err != nil { 245 return nil, fmt.Errorf("unable to marshal container headers") 246 } 247 248 // Hash serialized proto 249 hash := sha512.Sum512(header) 250 251 // No error 252 return hash[:], nil 253 } 254 255 func computeProtectedHash(headerHash, content []byte) []byte { 256 // Prepare protected content 257 protected := bytes.Buffer{} 258 protected.Write([]byte("harp fips encrypted signature")) 259 protected.WriteByte(0x00) 260 protected.Write(headerHash) 261 contentHash := sha512.Sum512(content) 262 protected.Write(contentHash[:]) 263 264 // No error 265 return protected.Bytes() 266 } 267 268 func packRecipient(rand io.Reader, payloadKey *[32]byte, ephPrivKey *ecdsa.PrivateKey, peerPublicKey *ecdsa.PublicKey, preSharedKey *[preSharedKeySize]byte) (*containerv1.Recipient, error) { 269 // Check arguments 270 if payloadKey == nil { 271 return nil, fmt.Errorf("unable to proceed with nil payload key") 272 } 273 if ephPrivKey == nil { 274 return nil, fmt.Errorf("unable to proceed with nil private key") 275 } 276 if peerPublicKey == nil { 277 return nil, fmt.Errorf("unable to proceed with nil public key") 278 } 279 280 // Create identifier 281 recipientKey, err := deriveSharedKeyFromRecipient(peerPublicKey, ephPrivKey, preSharedKey) 282 if err != nil { 283 return nil, fmt.Errorf("unable to execute key agreement: %w", err) 284 } 285 286 // Calculate identifier 287 identifier, err := keyIdentifierFromDerivedKey(recipientKey, preSharedKey) 288 if err != nil { 289 return nil, fmt.Errorf("unable to derive key identifier: %w", err) 290 } 291 292 // Encrypt the payload key 293 encryptedKey, err := encrypt(rand, payloadKey[:], recipientKey) 294 if err != nil { 295 return nil, fmt.Errorf("unable to encrypt payload key for recipient: %w", err) 296 } 297 298 // Pack recipient 299 recipient := &containerv1.Recipient{ 300 Identifier: identifier, 301 Key: encryptedKey, 302 } 303 304 // Return recipient 305 return recipient, nil 306 } 307 308 func deriveSharedKeyFromRecipient(publicKey *ecdsa.PublicKey, privateKey *ecdsa.PrivateKey, preSharedKey *[preSharedKeySize]byte) (*[32]byte, error) { 309 // Compute Z - ECDH(localPrivate, remotePublic) 310 Z, _ := privateKey.Curve.ScalarMult(publicKey.X, publicKey.Y, privateKey.D.Bytes()) 311 312 // Prepare info: ( AlgorithmID || PartyInfo || KeyLength ) 313 fixedInfo := []byte{} 314 fixedInfo = append(fixedInfo, lengthPrefixedArray([]byte("A256CTR"))...) 315 fixedInfo = append(fixedInfo, uint32ToBytes(encryptionKeySize)...) 316 317 // HKDF-HMAC-SHA512 318 kdf := hkdf.New(sha512.New, Z.Bytes(), nil, fixedInfo) 319 320 var sharedSecret [encryptionKeySize]byte 321 if _, err := io.ReadFull(kdf, sharedSecret[:]); err != nil { 322 return nil, fmt.Errorf("unable to derive shared secret: %w", err) 323 } 324 325 // Apply psk, this will act as a second knowledge factor to allow container 326 // unseal 327 if preSharedKey != nil { 328 // Compute HMAC-SHA512 of the shared secret. 329 pskh := hmac.New(sha512.New, preSharedKey[:]) 330 pskh.Write([]byte{0x00, 0x00, 0x00, 0x01}) 331 pskh.Write(sharedSecret[:]) 332 skHash := pskh.Sum(nil) 333 copy(sharedSecret[:], skHash[:encryptionKeySize]) 334 } 335 336 // No error 337 return &sharedSecret, nil 338 } 339 340 func keyIdentifierFromDerivedKey(derivedKey *[32]byte, preSharedKey *[preSharedKeySize]byte) ([]byte, error) { 341 // HMAC-SHA512 342 h := hmac.New(sha512.New, []byte("harp signcryption box key identifier")) 343 if _, err := h.Write(derivedKey[:]); err != nil { 344 return nil, fmt.Errorf("unable to generate recipient identifier") 345 } 346 347 // Apply psk if specified 348 if preSharedKey != nil { 349 if _, err := h.Write(preSharedKey[:]); err != nil { 350 return nil, fmt.Errorf("unable to generate recipient identifier") 351 } 352 } 353 354 // Return 32 bytes truncated hash. 355 return h.Sum(nil)[0:encryptionKeySize], nil 356 } 357 358 func lengthPrefixedArray(value []byte) []byte { 359 if len(value) == 0 { 360 return []byte{} 361 } 362 result := make([]byte, 4) 363 binary.BigEndian.PutUint32(result, uint32(len(value))) 364 365 //nolint:makezero // expected behavior 366 return append(result, value...) 367 } 368 369 func uint32ToBytes(value uint32) []byte { 370 result := make([]byte, 4) 371 binary.BigEndian.PutUint32(result, value) 372 373 return result 374 } 375 376 func kdf(key *[32]byte, n []byte) (ek, n2, ak []byte, err error) { 377 // Check arguments 378 if key == nil { 379 return nil, nil, nil, errors.New("unable to derive keys from a nil seed") 380 } 381 382 // Prepare HKDF-HMAC-SHA384 383 encKDF := hkdf.New(sha512.New384, key[:], nil, append([]byte("harp-encryption-key-v2"), n...)) 384 385 // Derive encryption key 386 tmp := make([]byte, encryptionKeySize+nonceSize) 387 if _, err := io.ReadFull(encKDF, tmp); err != nil { 388 return nil, nil, nil, fmt.Errorf("unable to generate encryption key from seed: %w", err) 389 } 390 391 // Split encryption key (Ek) and nonce (n2) 392 ek = tmp[:encryptionKeySize] 393 n2 = tmp[encryptionKeySize:] 394 395 // Derive authentication key 396 authKDF := hkdf.New(sha512.New384, key[:], nil, append([]byte("harp-auth-key-for-aead"), n...)) 397 398 // Derive authentication key 399 ak = make([]byte, nonceSize) 400 if _, err := io.ReadFull(authKDF, ak); err != nil { 401 return nil, nil, nil, fmt.Errorf("unable to generate authentication key from seed: %w", err) 402 } 403 404 // No error 405 return ek, n2, ak, nil 406 } 407 408 func mac(ak, n, c []byte) ([]byte, error) { 409 // Compute pre-authenticated content 410 preAuth, err := pae([]byte("harp-authentication-tag-v2"), n, c) 411 if err != nil { 412 return nil, err 413 } 414 415 // Compute MAC 416 mac := hmac.New(sha512.New384, ak) 417 418 // Hash pre-authentication content 419 mac.Write(preAuth) 420 421 // No error 422 return mac.Sum(nil), nil 423 } 424 425 func pae(pieces ...[]byte) ([]byte, error) { 426 output := &bytes.Buffer{} 427 428 // Encode piece count 429 count := len(pieces) 430 if err := binary.Write(output, binary.LittleEndian, uint64(count)); err != nil { 431 return nil, err 432 } 433 434 // For each element 435 for i := range pieces { 436 // Encode size 437 if err := binary.Write(output, binary.LittleEndian, uint64(len(pieces[i]))); err != nil { 438 return nil, err 439 } 440 441 // Encode data 442 if _, err := output.Write(pieces[i]); err != nil { 443 return nil, err 444 } 445 } 446 447 // No error 448 return output.Bytes(), nil 449 }