2022-07-07 20:11:50 +00:00
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package keykeeperv1
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"fmt"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/google/uuid"
2022-11-05 17:32:58 +00:00
"io/ioutil"
"os"
2022-07-07 20:11:50 +00:00
"os/exec"
"peridot.resf.org/peridot/db/models"
"peridot.resf.org/utils"
"strings"
"sync"
)
// LoadedKey keeps the key and some other information in memory
// todo(mustafa): Add TTL, rotation check, etc.
type LoadedKey struct {
sync . Mutex
keyUuid uuid . UUID
gpgId string
}
func logCmdRun ( cmd * exec . Cmd ) ( * bytes . Buffer , error ) {
var outBuf bytes . Buffer
cmd . Stdout = & outBuf
cmd . Stderr = & outBuf
return & outBuf , cmd . Run ( )
}
func gpgCmdEnv ( cmd * exec . Cmd ) * exec . Cmd {
cmd . Env = append ( cmd . Env , "GNUPGHOME=/keykeeper/gpg" )
return cmd
}
func ( s * Server ) importGpgKey ( armoredKey string ) error {
cmd := gpgCmdEnv ( exec . Command ( "gpg" , "--batch" , "--yes" , "--import" , "-" ) )
cmd . Stdin = strings . NewReader ( armoredKey )
out , err := logCmdRun ( cmd )
if err != nil {
s . log . Errorf ( "failed to import gpg key: %s" , out . String ( ) )
}
return err
}
2022-11-05 17:32:58 +00:00
func ( s * Server ) importRpmKey ( publicKey string ) error {
tmpFile , err := ioutil . TempFile ( "/tmp" , "peridot-key-" )
if err != nil {
return err
}
defer os . Remove ( tmpFile . Name ( ) )
_ , err = tmpFile . Write ( [ ] byte ( publicKey ) )
if err != nil {
return err
}
cmd := gpgCmdEnv ( exec . Command ( "rpm" , "--import" , tmpFile . Name ( ) ) )
out , err := logCmdRun ( cmd )
if err != nil {
s . log . Errorf ( "failed to import rpm key: %s" , out . String ( ) )
}
return err
}
2022-07-07 20:11:50 +00:00
// WarmGPGKey warms up a specific GPG key
// This involves shelling out to GPG to import the key
func ( s * Server ) WarmGPGKey ( key string , armoredKey string , gpgKey * crypto . Key , db * models . Key ) ( * LoadedKey , error ) {
2022-08-16 13:45:07 +00:00
cachedKeyAny , ok := s . keys . Load ( key )
2022-07-07 20:11:50 +00:00
// This means that the key is already loaded
2022-08-16 13:45:07 +00:00
if ok {
return cachedKeyAny . ( * LoadedKey ) , nil
2022-07-07 20:11:50 +00:00
}
err := s . importGpgKey ( armoredKey )
if err != nil {
return nil , err
}
2022-11-05 17:32:58 +00:00
err = s . importRpmKey ( db . PublicKey )
if err != nil {
return nil , err
}
2022-08-16 13:45:07 +00:00
cachedKey := & LoadedKey {
keyUuid : db . ID ,
gpgId : gpgKey . GetHexKeyID ( ) ,
2022-07-07 20:11:50 +00:00
}
2022-08-16 13:45:07 +00:00
s . keys . Store ( key , cachedKey )
2022-07-07 20:11:50 +00:00
2022-08-16 13:45:07 +00:00
return cachedKey , nil
2022-07-07 20:11:50 +00:00
}
// EnsureGPGKey ensures that the key is loaded
func ( s * Server ) EnsureGPGKey ( key string ) ( * LoadedKey , error ) {
2022-08-16 13:45:07 +00:00
cachedKeyAny , ok := s . keys . Load ( key )
if ok {
return cachedKeyAny . ( * LoadedKey ) , nil
2022-07-07 20:11:50 +00:00
}
// Key not found in cache, fetch from database
// Fetch the encryption key, nonce and external key ID from the database
k , err := s . db . GetKeyByName ( key )
if err != nil {
return nil , err
}
encBytes , err := hex . DecodeString ( k . EncKey )
if err != nil {
return nil , err
}
nonce , err := hex . DecodeString ( k . Nonce )
if err != nil {
return nil , err
}
// Fetch the encrypted key from secret store
store := s . stores [ k . ExtStoreType ]
if store == nil {
return nil , fmt . Errorf ( "no store found for type %s, which key with name \"%s\" relies on. manual rotation may be required #TODO-DOCS" , k . ExtStoreType , k . Name )
}
encryptedArmoredKeyHex , err := store . Get ( k . ExtStoreId )
if err != nil {
return nil , err
}
encryptedArmoredKey , err := hex . DecodeString ( encryptedArmoredKeyHex )
if err != nil {
return nil , err
}
// Decrypt the key
block , err := aes . NewCipher ( encBytes )
if err != nil {
return nil , err
}
gcm , err := cipher . NewGCM ( block )
if err != nil {
return nil , err
}
armoredKey , err := gcm . Open ( nil , nonce , encryptedArmoredKey , nil )
if err != nil {
return nil , err
}
keyObj , err := crypto . NewKeyFromArmored ( string ( armoredKey ) )
if err != nil {
s . log . Errorf ( "could not get key from armored string: %v" , err )
return nil , utils . InternalError
}
loadedKey , err := s . WarmGPGKey ( key , string ( armoredKey ) , keyObj , k )
if err != nil {
return nil , err
}
return loadedKey , nil
}