github.com/zntrio/harp/v2@v2.0.9/pkg/container/seal/v2/unseal.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 "crypto/ecdsa" 22 "crypto/elliptic" 23 "crypto/sha512" 24 "encoding/base64" 25 "errors" 26 "fmt" 27 "math/big" 28 "strings" 29 30 "github.com/awnumar/memguard" 31 "google.golang.org/protobuf/proto" 32 33 containerv1 "github.com/zntrio/harp/v2/api/gen/go/harp/container/v1" 34 "github.com/zntrio/harp/v2/pkg/sdk/types" 35 ) 36 37 // Unseal a sealed container with the given identity. 38 func (a *adapter) Unseal(container *containerv1.Container, identity *memguard.LockedBuffer) (*containerv1.Container, error) { 39 return a.unseal(container, identity, nil) 40 } 41 42 // Unseal a sealed container with the given identity and the given preshared key. 43 func (a *adapter) UnsealWithPSK(container *containerv1.Container, identity, preSharedKey *memguard.LockedBuffer) (*containerv1.Container, error) { 44 return a.unseal(container, identity, preSharedKey) 45 } 46 47 //nolint:funlen,gocyclo // To refactor 48 func (a *adapter) unseal(container *containerv1.Container, identity, preSharedKey *memguard.LockedBuffer) (*containerv1.Container, error) { 49 // Check parameters 50 if types.IsNil(container) { 51 return nil, fmt.Errorf("unable to process nil container") 52 } 53 if types.IsNil(container.Headers) { 54 return nil, fmt.Errorf("unable to process nil container headers") 55 } 56 if identity == nil { 57 return nil, fmt.Errorf("unable to process without container key") 58 } 59 60 // Check headers 61 if container.Headers.ContentType != containerSealedContentType { 62 return nil, fmt.Errorf("unable to unseal container") 63 } 64 65 // Check ephemeral container public encryption key 66 if len(container.Headers.EncryptionPublicKey) != publicKeySize { 67 return nil, fmt.Errorf("invalid container public size") 68 } 69 70 // Decode public key 71 var publicKey ecdsa.PublicKey 72 publicKey.Curve = elliptic.P384() 73 publicKey.X, publicKey.Y = elliptic.UnmarshalCompressed(elliptic.P384(), container.Headers.EncryptionPublicKey) 74 if publicKey.X == nil { 75 return nil, errors.New("invalid container signing public key") 76 } 77 78 // Decode private key 79 privRaw, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(identity.String(), PrivateKeyPrefix)) 80 if err != nil { 81 return nil, fmt.Errorf("unable to decode private key: %w", err) 82 } 83 if len(privRaw) != privateKeySize { 84 return nil, fmt.Errorf("invalid identity private key length") 85 } 86 var pk ecdsa.PrivateKey 87 pk.PublicKey.Curve = elliptic.P384() 88 pk.D = big.NewInt(0).SetBytes(privRaw) 89 90 // Compute preshared key 91 var psk *[preSharedKeySize]byte 92 if preSharedKey != nil { 93 psk = pskStretch(preSharedKey.Bytes(), container.Headers.EncryptionPublicKey) 94 } 95 96 // Precompute identifier 97 derivedKey, err := deriveSharedKeyFromRecipient(&publicKey, &pk, psk) 98 if err != nil { 99 return nil, fmt.Errorf("unable to execute key agreement: %w", err) 100 } 101 102 // Try recipients 103 payloadKey, err := tryRecipientKeys(derivedKey, container.Headers.Recipients, psk) 104 if err != nil { 105 return nil, fmt.Errorf("error occurred during recipient key tests: %w", err) 106 } 107 108 // Check private key 109 if len(payloadKey) != encryptionKeySize { 110 return nil, fmt.Errorf("invalid encryption key size") 111 } 112 var encryptionKey [encryptionKeySize]byte 113 copy(encryptionKey[:], payloadKey[:encryptionKeySize]) 114 115 // Decrypt signing public key 116 containerSignKeyRaw, err := decrypt(container.Headers.ContainerBox, payloadKey) 117 if err != nil { 118 return nil, fmt.Errorf("invalid container key") 119 } 120 if len(containerSignKeyRaw) != publicKeySize { 121 return nil, fmt.Errorf("invalid signature key size") 122 } 123 124 // Compute headers hash 125 headerHash, err := computeHeaderHash(container.Headers) 126 if err != nil { 127 return nil, fmt.Errorf("unable to compute header hash: %w", err) 128 } 129 130 // Decrypt payload 131 payloadRaw, err := decrypt(container.Raw, &encryptionKey) 132 if err != nil || len(payloadRaw) < signatureSize { 133 return nil, fmt.Errorf("invalid ciphered content") 134 } 135 136 // Prepare protected content 137 protectedHash := computeProtectedHash(headerHash, payloadRaw) 138 139 // Extract signature / content 140 detachedSig := payloadRaw[:signatureSize] 141 content := payloadRaw[signatureSize:] 142 143 // Compute SHA-384 checksum 144 digest := sha512.Sum384(protectedHash) 145 146 var ( 147 r = big.NewInt(0).SetBytes(detachedSig[:48]) 148 s = big.NewInt(0).SetBytes(detachedSig[48:]) 149 ) 150 // Validate signature 151 if ecdsa.Verify(&publicKey, digest[:], r, s) { 152 return nil, fmt.Errorf("unable to verify protected content: %w", err) 153 } 154 155 // Unmarshal inner container 156 out := &containerv1.Container{} 157 if err := proto.Unmarshal(content, out); err != nil { 158 return nil, fmt.Errorf("unable to unpack inner content: %w", err) 159 } 160 161 // No error 162 return out, nil 163 }