mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-22 13:11:27 +00:00
490 lines
13 KiB
Go
490 lines
13 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ProtonMail/gopenpgp/v2/armor"
|
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
|
"github.com/pkg/errors"
|
|
|
|
openpgp "github.com/ProtonMail/go-crypto/openpgp"
|
|
packet "github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
)
|
|
|
|
// Key contains a single private or public key.
|
|
type Key struct {
|
|
// PGP entities in this keyring.
|
|
entity *openpgp.Entity
|
|
}
|
|
|
|
// --- Create Key object
|
|
|
|
// NewKeyFromArmoredReader reads an armored data into a key.
|
|
func NewKeyFromArmoredReader(r io.Reader) (key *Key, err error) {
|
|
key = &Key{}
|
|
err = key.readFrom(r, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// NewKeyFromReader reads binary data into a Key object.
|
|
func NewKeyFromReader(r io.Reader) (key *Key, err error) {
|
|
key = &Key{}
|
|
err = key.readFrom(r, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// NewKey creates a new key from the first key in the unarmored binary data.
|
|
func NewKey(binKeys []byte) (key *Key, err error) {
|
|
return NewKeyFromReader(bytes.NewReader(clone(binKeys)))
|
|
}
|
|
|
|
// NewKeyFromArmored creates a new key from the first key in an armored string.
|
|
func NewKeyFromArmored(armored string) (key *Key, err error) {
|
|
return NewKeyFromArmoredReader(strings.NewReader(armored))
|
|
}
|
|
|
|
func NewKeyFromEntity(entity *openpgp.Entity) (*Key, error) {
|
|
if entity == nil {
|
|
return nil, errors.New("gopenpgp: nil entity provided")
|
|
}
|
|
return &Key{entity: entity}, nil
|
|
}
|
|
|
|
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
|
|
func GenerateRSAKeyWithPrimes(
|
|
name, email string,
|
|
bits int,
|
|
primeone, primetwo, primethree, primefour []byte,
|
|
) (*Key, error) {
|
|
return generateKey(name, email, "rsa", bits, primeone, primetwo, primethree, primefour)
|
|
}
|
|
|
|
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
|
|
// If keyType is "rsa", bits is the RSA bitsize of the key.
|
|
// If keyType is "x25519" bits is unused.
|
|
func GenerateKey(name, email string, keyType string, bits int) (*Key, error) {
|
|
return generateKey(name, email, keyType, bits, nil, nil, nil, nil)
|
|
}
|
|
|
|
// --- Operate on key
|
|
|
|
// Copy creates a deep copy of the key.
|
|
func (key *Key) Copy() (*Key, error) {
|
|
serialized, err := key.Serialize()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewKey(serialized)
|
|
}
|
|
|
|
// Lock locks a copy of the key.
|
|
func (key *Key) Lock(passphrase []byte) (*Key, error) {
|
|
unlocked, err := key.IsUnlocked()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !unlocked {
|
|
return nil, errors.New("gopenpgp: key is not unlocked")
|
|
}
|
|
|
|
lockedKey, err := key.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if passphrase == nil {
|
|
return lockedKey, nil
|
|
}
|
|
|
|
if lockedKey.entity.PrivateKey != nil && !lockedKey.entity.PrivateKey.Dummy() {
|
|
err = lockedKey.entity.PrivateKey.Encrypt(passphrase)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in locking key")
|
|
}
|
|
}
|
|
|
|
for _, sub := range lockedKey.entity.Subkeys {
|
|
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() {
|
|
if err := sub.PrivateKey.Encrypt(passphrase); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in locking sub key")
|
|
}
|
|
}
|
|
}
|
|
|
|
locked, err := lockedKey.IsLocked()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !locked {
|
|
return nil, errors.New("gopenpgp: unable to lock key")
|
|
}
|
|
|
|
return lockedKey, nil
|
|
}
|
|
|
|
// Unlock unlocks a copy of the key.
|
|
func (key *Key) Unlock(passphrase []byte) (*Key, error) {
|
|
isLocked, err := key.IsLocked()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isLocked {
|
|
if passphrase == nil {
|
|
return key.Copy()
|
|
}
|
|
return nil, errors.New("gopenpgp: key is not locked")
|
|
}
|
|
|
|
unlockedKey, err := key.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if unlockedKey.entity.PrivateKey != nil && !unlockedKey.entity.PrivateKey.Dummy() {
|
|
err = unlockedKey.entity.PrivateKey.Decrypt(passphrase)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in unlocking key")
|
|
}
|
|
}
|
|
|
|
for _, sub := range unlockedKey.entity.Subkeys {
|
|
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() {
|
|
if err := sub.PrivateKey.Decrypt(passphrase); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in unlocking sub key")
|
|
}
|
|
}
|
|
}
|
|
|
|
isUnlocked, err := unlockedKey.IsUnlocked()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isUnlocked {
|
|
return nil, errors.New("gopenpgp: unable to unlock key")
|
|
}
|
|
|
|
return unlockedKey, nil
|
|
}
|
|
|
|
// --- Export key
|
|
|
|
func (key *Key) Serialize() ([]byte, error) {
|
|
var buffer bytes.Buffer
|
|
var err error
|
|
|
|
if key.entity.PrivateKey == nil {
|
|
err = key.entity.Serialize(&buffer)
|
|
} else {
|
|
err = key.entity.SerializePrivateWithoutSigning(&buffer, nil)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in serializing key")
|
|
}
|
|
|
|
return buffer.Bytes(), nil
|
|
}
|
|
|
|
// Armor returns the armored key as a string with default gopenpgp headers.
|
|
func (key *Key) Armor() (string, error) {
|
|
serialized, err := key.Serialize()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if key.IsPrivate() {
|
|
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
|
|
}
|
|
|
|
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
|
|
}
|
|
|
|
// ArmorWithCustomHeaders returns the armored key as a string, with
|
|
// the given headers. Empty parameters are omitted from the headers.
|
|
func (key *Key) ArmorWithCustomHeaders(comment, version string) (string, error) {
|
|
serialized, err := key.Serialize()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PrivateKeyHeader, version, comment)
|
|
}
|
|
|
|
// GetArmoredPublicKey returns the armored public keys from this keyring.
|
|
func (key *Key) GetArmoredPublicKey() (s string, err error) {
|
|
serialized, err := key.GetPublicKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
|
|
}
|
|
|
|
// GetArmoredPublicKeyWithCustomHeaders returns the armored public key as a string, with
|
|
// the given headers. Empty parameters are omitted from the headers.
|
|
func (key *Key) GetArmoredPublicKeyWithCustomHeaders(comment, version string) (string, error) {
|
|
serialized, err := key.GetPublicKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PublicKeyHeader, version, comment)
|
|
}
|
|
|
|
// GetPublicKey returns the unarmored public keys from this keyring.
|
|
func (key *Key) GetPublicKey() (b []byte, err error) {
|
|
var outBuf bytes.Buffer
|
|
if err = key.entity.Serialize(&outBuf); err != nil {
|
|
return nil, errors.Wrap(err, "gopenpgp: error in serializing public key")
|
|
}
|
|
|
|
return outBuf.Bytes(), nil
|
|
}
|
|
|
|
// --- Key object properties
|
|
|
|
// CanVerify returns true if any of the subkeys can be used for verification.
|
|
func (key *Key) CanVerify() bool {
|
|
_, canVerify := key.entity.SigningKey(getNow())
|
|
return canVerify
|
|
}
|
|
|
|
// CanEncrypt returns true if any of the subkeys can be used for encryption.
|
|
func (key *Key) CanEncrypt() bool {
|
|
_, canEncrypt := key.entity.EncryptionKey(getNow())
|
|
return canEncrypt
|
|
}
|
|
|
|
// IsExpired checks whether the key is expired.
|
|
func (key *Key) IsExpired() bool {
|
|
i := key.entity.PrimaryIdentity()
|
|
return key.entity.PrimaryKey.KeyExpired(i.SelfSignature, getNow()) || // primary key has expired
|
|
i.SelfSignature.SigExpired(getNow()) // user ID self-signature has expired
|
|
}
|
|
|
|
// IsRevoked checks whether the key or the primary identity has a valid revocation signature.
|
|
func (key *Key) IsRevoked() bool {
|
|
return key.entity.Revoked(getNow()) || key.entity.PrimaryIdentity().Revoked(getNow())
|
|
}
|
|
|
|
// IsPrivate returns true if the key is private.
|
|
func (key *Key) IsPrivate() bool {
|
|
return key.entity.PrivateKey != nil
|
|
}
|
|
|
|
// IsLocked checks if a private key is locked.
|
|
func (key *Key) IsLocked() (bool, error) {
|
|
if key.entity.PrivateKey == nil {
|
|
return true, errors.New("gopenpgp: a public key cannot be locked")
|
|
}
|
|
|
|
encryptedKeys := 0
|
|
|
|
for _, sub := range key.entity.Subkeys {
|
|
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
|
|
encryptedKeys++
|
|
}
|
|
}
|
|
|
|
if key.entity.PrivateKey.Encrypted {
|
|
encryptedKeys++
|
|
}
|
|
|
|
return encryptedKeys > 0, nil
|
|
}
|
|
|
|
// IsUnlocked checks if a private key is unlocked.
|
|
func (key *Key) IsUnlocked() (bool, error) {
|
|
if key.entity.PrivateKey == nil {
|
|
return true, errors.New("gopenpgp: a public key cannot be unlocked")
|
|
}
|
|
|
|
encryptedKeys := 0
|
|
|
|
for _, sub := range key.entity.Subkeys {
|
|
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
|
|
encryptedKeys++
|
|
}
|
|
}
|
|
|
|
if key.entity.PrivateKey.Encrypted {
|
|
encryptedKeys++
|
|
}
|
|
|
|
return encryptedKeys == 0, nil
|
|
}
|
|
|
|
// Check verifies if the public keys match the private key parameters by
|
|
// signing and verifying.
|
|
// Deprecated: all keys are now checked on parsing.
|
|
func (key *Key) Check() (bool, error) {
|
|
return true, nil
|
|
}
|
|
|
|
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
|
|
func (key *Key) PrintFingerprints() {
|
|
for _, subKey := range key.entity.Subkeys {
|
|
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
|
|
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint))
|
|
}
|
|
}
|
|
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint))
|
|
}
|
|
|
|
// GetHexKeyID returns the key ID, hex encoded as a string.
|
|
func (key *Key) GetHexKeyID() string {
|
|
return keyIDToHex(key.GetKeyID())
|
|
}
|
|
|
|
// GetKeyID returns the key ID, encoded as 8-byte int.
|
|
func (key *Key) GetKeyID() uint64 {
|
|
return key.entity.PrimaryKey.KeyId
|
|
}
|
|
|
|
// GetFingerprint gets the fingerprint from the key.
|
|
func (key *Key) GetFingerprint() string {
|
|
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint)
|
|
}
|
|
|
|
// GetSHA256Fingerprints computes the SHA256 fingerprints of the key and subkeys.
|
|
func (key *Key) GetSHA256Fingerprints() (fingerprints []string) {
|
|
fingerprints = append(fingerprints, hex.EncodeToString(getSHA256FingerprintBytes(key.entity.PrimaryKey)))
|
|
for _, sub := range key.entity.Subkeys {
|
|
fingerprints = append(fingerprints, hex.EncodeToString(getSHA256FingerprintBytes(sub.PublicKey)))
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetEntity gets x/crypto Entity object.
|
|
func (key *Key) GetEntity() *openpgp.Entity {
|
|
return key.entity
|
|
}
|
|
|
|
// ToPublic returns the corresponding public key of the given private key.
|
|
func (key *Key) ToPublic() (publicKey *Key, err error) {
|
|
if !key.IsPrivate() {
|
|
return nil, errors.New("gopenpgp: key is already public")
|
|
}
|
|
|
|
publicKey, err = key.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
publicKey.ClearPrivateParams()
|
|
return
|
|
}
|
|
|
|
// --- Internal methods
|
|
|
|
// getSHA256FingerprintBytes computes the SHA256 fingerprint of a public key
|
|
// object.
|
|
func getSHA256FingerprintBytes(pk *packet.PublicKey) []byte {
|
|
fingerPrint := sha256.New()
|
|
|
|
// Hashing can't return an error, and has already been done when parsing the key,
|
|
// hence the error is nil
|
|
_ = pk.SerializeForHash(fingerPrint)
|
|
return fingerPrint.Sum(nil)
|
|
}
|
|
|
|
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
|
|
func (key *Key) readFrom(r io.Reader, armored bool) error {
|
|
var err error
|
|
var entities openpgp.EntityList
|
|
if armored {
|
|
entities, err = openpgp.ReadArmoredKeyRing(r)
|
|
} else {
|
|
entities, err = openpgp.ReadKeyRing(r)
|
|
}
|
|
if err != nil {
|
|
return errors.Wrap(err, "gopenpgp: error in reading key ring")
|
|
}
|
|
|
|
if len(entities) > 1 {
|
|
return errors.New("gopenpgp: the key contains too many entities")
|
|
}
|
|
|
|
if len(entities) == 0 {
|
|
return errors.New("gopenpgp: the key does not contain any entity")
|
|
}
|
|
|
|
key.entity = entities[0]
|
|
return nil
|
|
}
|
|
|
|
func generateKey(
|
|
name, email string,
|
|
keyType string,
|
|
bits int,
|
|
prime1, prime2, prime3, prime4 []byte,
|
|
) (*Key, error) {
|
|
if len(email) == 0 && len(name) == 0 {
|
|
return nil, errors.New("gopenpgp: neither name nor email set.")
|
|
}
|
|
|
|
comments := ""
|
|
|
|
cfg := &packet.Config{
|
|
Algorithm: packet.PubKeyAlgoRSA,
|
|
RSABits: bits,
|
|
Time: getKeyGenerationTimeGenerator(),
|
|
DefaultHash: crypto.SHA256,
|
|
DefaultCipher: packet.CipherAES256,
|
|
DefaultCompressionAlgo: packet.CompressionZLIB,
|
|
}
|
|
|
|
if keyType == "x25519" {
|
|
cfg.Algorithm = packet.PubKeyAlgoEdDSA
|
|
}
|
|
|
|
if prime1 != nil && prime2 != nil && prime3 != nil && prime4 != nil {
|
|
var bigPrimes [4]*big.Int
|
|
bigPrimes[0] = new(big.Int)
|
|
bigPrimes[0].SetBytes(prime1)
|
|
bigPrimes[1] = new(big.Int)
|
|
bigPrimes[1].SetBytes(prime2)
|
|
bigPrimes[2] = new(big.Int)
|
|
bigPrimes[2].SetBytes(prime3)
|
|
bigPrimes[3] = new(big.Int)
|
|
bigPrimes[3].SetBytes(prime4)
|
|
|
|
cfg.RSAPrimes = bigPrimes[:]
|
|
}
|
|
|
|
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "gopengpp: error in encoding new entity")
|
|
}
|
|
|
|
if newEntity.PrivateKey == nil {
|
|
return nil, errors.New("gopenpgp: error in generating private key")
|
|
}
|
|
|
|
return NewKeyFromEntity(newEntity)
|
|
}
|
|
|
|
// keyIDToHex casts a keyID to hex with the correct padding.
|
|
func keyIDToHex(keyID uint64) string {
|
|
return fmt.Sprintf("%016v", strconv.FormatUint(keyID, 16))
|
|
}
|