pkg/container/seal/v2/key.go (76 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 v2 import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/hmac" "crypto/rand" "crypto/sha512" "encoding/base64" "fmt" "strings" "github.com/awnumar/memguard" "github.com/elastic/harp/pkg/container/seal" "golang.org/x/crypto/pbkdf2" ) const ( PublicKeyPrefix = "v2.sk." PrivateKeyPrefix = "v2.ck." ) // CenerateKey create an ECDSA P-384 key pair used as container identifier. func (a *adapter) GenerateKey(fopts ...seal.GenerateOption) (publicKey, privateKey string, err error) { // Prepare defaults opts := &seal.GenerateOptions{ DCKDMasterKey: nil, DCKDTarget: "", RandomSource: rand.Reader, } // Apply optional parameters for _, f := range fopts { f(opts) } // Master key derivation if opts.DCKDMasterKey != nil { // PBKDF2-SHA512(masterKey, HMAC-SHA-512('harp deterministic salt v2', Target), 250000, 64) // Don't clean bytes, already done by memguard. masterKey := opts.DCKDMasterKey.Bytes() if len(masterKey) < 32 { return "", "", fmt.Errorf("the master key must be 32 bytes long at least") } // Generate deterministic salt h := hmac.New(sha512.New, []byte("harp deterministic salt v2")) h.Write([]byte(opts.DCKDTarget)) salt := h.Sum(nil) defer memguard.WipeBytes(salt) // Derive deterministic container key using PBKDF2-SHA512 dk := pbkdf2.Key(masterKey[:32], salt, 250000, 64, sha512.New) defer memguard.WipeBytes(dk) // Assign to seed opts.RandomSource = bytes.NewBuffer(dk) } // Generate ECDSA P-384 container key pair priv, errGen := ecdsa.GenerateKey(elliptic.P384(), opts.RandomSource) if errGen != nil { return "", "", fmt.Errorf("unable to generate container key: %w", errGen) } // Encode keys encodedPub := append([]byte(PublicKeyPrefix), base64.RawURLEncoding.EncodeToString(elliptic.MarshalCompressed(priv.Curve, priv.PublicKey.X, priv.PublicKey.Y))...) encodedPriv := append([]byte(PrivateKeyPrefix), base64.RawURLEncoding.EncodeToString(priv.D.Bytes())...) // No error return string(encodedPub), string(encodedPriv), nil } // PublicKeys return the appropriate key format used by the sealing strategy. func (a *adapter) publicKeys(keys ...string) ([]*ecdsa.PublicKey, error) { // v2.pk.[data] res := []*ecdsa.PublicKey{} for _, key := range keys { // Check key prefix if !strings.HasPrefix(key, PublicKeyPrefix) { return nil, fmt.Errorf("unsuppored public key '%s' for v2 seal algorithm", key) } // Remove prefix if exists key = strings.TrimPrefix(key, PublicKeyPrefix) // Decode key keyRaw, err := base64.RawURLEncoding.DecodeString(key) if err != nil { return nil, fmt.Errorf("unable to decode public key '%s': %w", key, err) } // Public key sanity checks if len(keyRaw) != publicKeySize { return nil, fmt.Errorf("invalid public key length for key '%s'", key) } // Decode the compressed point x, y := elliptic.UnmarshalCompressed(elliptic.P384(), keyRaw) if x == nil { return nil, fmt.Errorf("invalid public key '%s'", key) } // Reassemble the public key pub := ecdsa.PublicKey{ Curve: elliptic.P384(), X: x, Y: y, } // Append it to sealing keys res = append(res, &pub) } // No error return res, nil }