pkg/container/seal/v1/key.go (74 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 v1 import ( "bytes" "crypto/rand" "encoding/base64" "fmt" "strings" "github.com/awnumar/memguard" "github.com/elastic/harp/pkg/container/seal" "github.com/elastic/harp/pkg/sdk/security/crypto/extra25519" "golang.org/x/crypto/argon2" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/nacl/box" ) const ( PublicKeyPrefix = "v1.sk." PrivateKeyPrefix = "v1.ck." ) // ----------------------------------------------------------------------------- // CenerateKey create an X25519 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 { // Argon2ID(masterKey, Blake2B-512('harp deterministic salt v1', Target), 1, 64Mb, 4, 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, err := blake2b.New512([]byte("harp deterministic salt v1")) if err != nil { return "", "", fmt.Errorf("unable to initialize salt derivation: %w", err) } h.Write([]byte(opts.DCKDTarget)) salt := h.Sum(nil) defer memguard.WipeBytes(salt) // Derive deterministic container key using Argon2id dk := argon2.IDKey(masterKey[:32], salt, 1, 64*1024, 4, 64) defer memguard.WipeBytes(dk) // Assign to seed opts.RandomSource = bytes.NewBuffer(dk) } // Generate x25519 container key pair pub, priv, errGen := box.GenerateKey(opts.RandomSource) if errGen != nil { return "", "", fmt.Errorf("unable to generate container key: %w", errGen) } // Encode keys encodedPub := append([]byte(PublicKeyPrefix), base64.RawURLEncoding.EncodeToString(pub[:])...) encodedPriv := append([]byte(PrivateKeyPrefix), base64.RawURLEncoding.EncodeToString(priv[:])...) // 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) ([]*[32]byte, error) { // v1.pk.[data] res := []*[publicKeySize]byte{} for _, key := range keys { // Check key prefix if !strings.HasPrefix(key, PublicKeyPrefix) { return nil, fmt.Errorf("unsuppored public key '%s' for v1 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) } if extra25519.IsEdLowOrder(keyRaw) { return nil, fmt.Errorf("low order public key usage is forbidden for key '%s, try to generate a new one to fix the issue", key) } // Copy the public key var pk [publicKeySize]byte copy(pk[:], keyRaw[:publicKeySize]) // Append it to sealing keys res = append(res, &pk) } // No error return res, nil }