mirror of
https://github.com/cheat/cheat.git
synced 2024-11-22 22:11:35 +01:00
80c91cbdee
Integrate `go-git` into the application, and use it to `git clone` cheatsheets when the installer runs. Previously, the installer required that `git` be installed on the system `PATH`, so this change has to big advantages: 1. It removes that system dependency on `git` 2. It paves the way for implementing the `--update` command Additionally, `cheat` now performs a `--depth=1` clone when installing cheatsheets, which should at least somewhat improve installation times (especially on slow network connections).
404 lines
12 KiB
Go
404 lines
12 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
||
// Use of this source code is governed by a BSD-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package openpgp
|
||
|
||
import (
|
||
"crypto"
|
||
"crypto/rand"
|
||
"crypto/rsa"
|
||
goerrors "errors"
|
||
"io"
|
||
"math/big"
|
||
"time"
|
||
|
||
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||
)
|
||
|
||
// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
|
||
// single identity composed of the given full name, comment and email, any of
|
||
// which may be empty but must not contain any of "()<>\x00".
|
||
// If config is nil, sensible defaults will be used.
|
||
func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) {
|
||
creationTime := config.Now()
|
||
keyLifetimeSecs := config.KeyLifetime()
|
||
|
||
// Generate a primary signing key
|
||
primaryPrivRaw, err := newSigner(config)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw)
|
||
if config != nil && config.V5Keys {
|
||
primary.UpgradeToV5()
|
||
}
|
||
|
||
e := &Entity{
|
||
PrimaryKey: &primary.PublicKey,
|
||
PrivateKey: primary,
|
||
Identities: make(map[string]*Identity),
|
||
Subkeys: []Subkey{},
|
||
}
|
||
|
||
err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// NOTE: No key expiry here, but we will not return this subkey in EncryptionKey()
|
||
// if the primary/master key has expired.
|
||
err = e.addEncryptionSubkey(config, creationTime, 0)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return e, nil
|
||
}
|
||
|
||
func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error {
|
||
creationTime := config.Now()
|
||
keyLifetimeSecs := config.KeyLifetime()
|
||
return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs)
|
||
}
|
||
|
||
func (t *Entity) addUserId(name, comment, email string, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error {
|
||
uid := packet.NewUserId(name, comment, email)
|
||
if uid == nil {
|
||
return errors.InvalidArgumentError("user id field contained invalid characters")
|
||
}
|
||
|
||
if _, ok := t.Identities[uid.Id]; ok {
|
||
return errors.InvalidArgumentError("user id exist")
|
||
}
|
||
|
||
primary := t.PrivateKey
|
||
|
||
isPrimaryId := len(t.Identities) == 0
|
||
|
||
selfSignature := &packet.Signature{
|
||
Version: primary.PublicKey.Version,
|
||
SigType: packet.SigTypePositiveCert,
|
||
PubKeyAlgo: primary.PublicKey.PubKeyAlgo,
|
||
Hash: config.Hash(),
|
||
CreationTime: creationTime,
|
||
KeyLifetimeSecs: &keyLifetimeSecs,
|
||
IssuerKeyId: &primary.PublicKey.KeyId,
|
||
IssuerFingerprint: primary.PublicKey.Fingerprint,
|
||
IsPrimaryId: &isPrimaryId,
|
||
FlagsValid: true,
|
||
FlagSign: true,
|
||
FlagCertify: true,
|
||
MDC: true, // true by default, see 5.8 vs. 5.14
|
||
AEAD: config.AEAD() != nil,
|
||
V5Keys: config != nil && config.V5Keys,
|
||
}
|
||
|
||
// Set the PreferredHash for the SelfSignature from the packet.Config.
|
||
// If it is not the must-implement algorithm from rfc4880bis, append that.
|
||
selfSignature.PreferredHash = []uint8{hashToHashId(config.Hash())}
|
||
if config.Hash() != crypto.SHA256 {
|
||
selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256))
|
||
}
|
||
|
||
// Likewise for DefaultCipher.
|
||
selfSignature.PreferredSymmetric = []uint8{uint8(config.Cipher())}
|
||
if config.Cipher() != packet.CipherAES128 {
|
||
selfSignature.PreferredSymmetric = append(selfSignature.PreferredSymmetric, uint8(packet.CipherAES128))
|
||
}
|
||
|
||
// We set CompressionNone as the preferred compression algorithm because
|
||
// of compression side channel attacks, then append the configured
|
||
// DefaultCompressionAlgo if any is set (to signal support for cases
|
||
// where the application knows that using compression is safe).
|
||
selfSignature.PreferredCompression = []uint8{uint8(packet.CompressionNone)}
|
||
if config.Compression() != packet.CompressionNone {
|
||
selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(config.Compression()))
|
||
}
|
||
|
||
// And for DefaultMode.
|
||
selfSignature.PreferredAEAD = []uint8{uint8(config.AEAD().Mode())}
|
||
if config.AEAD().Mode() != packet.AEADModeEAX {
|
||
selfSignature.PreferredAEAD = append(selfSignature.PreferredAEAD, uint8(packet.AEADModeEAX))
|
||
}
|
||
|
||
// User ID binding signature
|
||
err := selfSignature.SignUserId(uid.Id, &primary.PublicKey, primary, config)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
t.Identities[uid.Id] = &Identity{
|
||
Name: uid.Id,
|
||
UserId: uid,
|
||
SelfSignature: selfSignature,
|
||
Signatures: []*packet.Signature{selfSignature},
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// AddSigningSubkey adds a signing keypair as a subkey to the Entity.
|
||
// If config is nil, sensible defaults will be used.
|
||
func (e *Entity) AddSigningSubkey(config *packet.Config) error {
|
||
creationTime := config.Now()
|
||
keyLifetimeSecs := config.KeyLifetime()
|
||
|
||
subPrivRaw, err := newSigner(config)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw)
|
||
|
||
subkey := Subkey{
|
||
PublicKey: &sub.PublicKey,
|
||
PrivateKey: sub,
|
||
Sig: &packet.Signature{
|
||
Version: e.PrimaryKey.Version,
|
||
CreationTime: creationTime,
|
||
KeyLifetimeSecs: &keyLifetimeSecs,
|
||
SigType: packet.SigTypeSubkeyBinding,
|
||
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
|
||
Hash: config.Hash(),
|
||
FlagsValid: true,
|
||
FlagSign: true,
|
||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||
EmbeddedSignature: &packet.Signature{
|
||
Version: e.PrimaryKey.Version,
|
||
CreationTime: creationTime,
|
||
SigType: packet.SigTypePrimaryKeyBinding,
|
||
PubKeyAlgo: sub.PublicKey.PubKeyAlgo,
|
||
Hash: config.Hash(),
|
||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||
},
|
||
},
|
||
}
|
||
if config != nil && config.V5Keys {
|
||
subkey.PublicKey.UpgradeToV5()
|
||
}
|
||
|
||
err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
subkey.PublicKey.IsSubkey = true
|
||
subkey.PrivateKey.IsSubkey = true
|
||
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
|
||
return err
|
||
}
|
||
|
||
e.Subkeys = append(e.Subkeys, subkey)
|
||
return nil
|
||
}
|
||
|
||
// AddEncryptionSubkey adds an encryption keypair as a subkey to the Entity.
|
||
// If config is nil, sensible defaults will be used.
|
||
func (e *Entity) AddEncryptionSubkey(config *packet.Config) error {
|
||
creationTime := config.Now()
|
||
keyLifetimeSecs := config.KeyLifetime()
|
||
return e.addEncryptionSubkey(config, creationTime, keyLifetimeSecs)
|
||
}
|
||
|
||
func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error {
|
||
subPrivRaw, err := newDecrypter(config)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw)
|
||
|
||
subkey := Subkey{
|
||
PublicKey: &sub.PublicKey,
|
||
PrivateKey: sub,
|
||
Sig: &packet.Signature{
|
||
Version: e.PrimaryKey.Version,
|
||
CreationTime: creationTime,
|
||
KeyLifetimeSecs: &keyLifetimeSecs,
|
||
SigType: packet.SigTypeSubkeyBinding,
|
||
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
|
||
Hash: config.Hash(),
|
||
FlagsValid: true,
|
||
FlagEncryptStorage: true,
|
||
FlagEncryptCommunications: true,
|
||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||
},
|
||
}
|
||
if config != nil && config.V5Keys {
|
||
subkey.PublicKey.UpgradeToV5()
|
||
}
|
||
|
||
subkey.PublicKey.IsSubkey = true
|
||
subkey.PrivateKey.IsSubkey = true
|
||
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
|
||
return err
|
||
}
|
||
|
||
e.Subkeys = append(e.Subkeys, subkey)
|
||
return nil
|
||
}
|
||
|
||
// Generates a signing key
|
||
func newSigner(config *packet.Config) (signer interface{}, err error) {
|
||
switch config.PublicKeyAlgorithm() {
|
||
case packet.PubKeyAlgoRSA:
|
||
bits := config.RSAModulusBits()
|
||
if bits < 1024 {
|
||
return nil, errors.InvalidArgumentError("bits must be >= 1024")
|
||
}
|
||
if config != nil && len(config.RSAPrimes) >= 2 {
|
||
primes := config.RSAPrimes[0:2]
|
||
config.RSAPrimes = config.RSAPrimes[2:]
|
||
return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes)
|
||
}
|
||
return rsa.GenerateKey(config.Random(), bits)
|
||
case packet.PubKeyAlgoEdDSA:
|
||
curve := ecc.FindEdDSAByGenName(string(config.CurveName()))
|
||
if curve == nil {
|
||
return nil, errors.InvalidArgumentError("unsupported curve")
|
||
}
|
||
|
||
priv, err := eddsa.GenerateKey(config.Random(), curve)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return priv, nil
|
||
case packet.PubKeyAlgoECDSA:
|
||
curve := ecc.FindECDSAByGenName(string(config.CurveName()))
|
||
if curve == nil {
|
||
return nil, errors.InvalidArgumentError("unsupported curve")
|
||
}
|
||
|
||
priv, err := ecdsa.GenerateKey(config.Random(), curve)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return priv, nil
|
||
default:
|
||
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
|
||
}
|
||
}
|
||
|
||
// Generates an encryption/decryption key
|
||
func newDecrypter(config *packet.Config) (decrypter interface{}, err error) {
|
||
switch config.PublicKeyAlgorithm() {
|
||
case packet.PubKeyAlgoRSA:
|
||
bits := config.RSAModulusBits()
|
||
if bits < 1024 {
|
||
return nil, errors.InvalidArgumentError("bits must be >= 1024")
|
||
}
|
||
if config != nil && len(config.RSAPrimes) >= 2 {
|
||
primes := config.RSAPrimes[0:2]
|
||
config.RSAPrimes = config.RSAPrimes[2:]
|
||
return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes)
|
||
}
|
||
return rsa.GenerateKey(config.Random(), bits)
|
||
case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA:
|
||
fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey
|
||
case packet.PubKeyAlgoECDH:
|
||
var kdf = ecdh.KDF{
|
||
Hash: algorithm.SHA512,
|
||
Cipher: algorithm.AES256,
|
||
}
|
||
curve := ecc.FindECDHByGenName(string(config.CurveName()))
|
||
if curve == nil {
|
||
return nil, errors.InvalidArgumentError("unsupported curve")
|
||
}
|
||
return ecdh.GenerateKey(config.Random(), curve, kdf)
|
||
default:
|
||
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
|
||
}
|
||
}
|
||
|
||
var bigOne = big.NewInt(1)
|
||
|
||
// generateRSAKeyWithPrimes generates a multi-prime RSA keypair of the
|
||
// given bit size, using the given random source and prepopulated primes.
|
||
func generateRSAKeyWithPrimes(random io.Reader, nprimes int, bits int, prepopulatedPrimes []*big.Int) (*rsa.PrivateKey, error) {
|
||
priv := new(rsa.PrivateKey)
|
||
priv.E = 65537
|
||
|
||
if nprimes < 2 {
|
||
return nil, goerrors.New("generateRSAKeyWithPrimes: nprimes must be >= 2")
|
||
}
|
||
|
||
if bits < 1024 {
|
||
return nil, goerrors.New("generateRSAKeyWithPrimes: bits must be >= 1024")
|
||
}
|
||
|
||
primes := make([]*big.Int, nprimes)
|
||
|
||
NextSetOfPrimes:
|
||
for {
|
||
todo := bits
|
||
// crypto/rand should set the top two bits in each prime.
|
||
// Thus each prime has the form
|
||
// p_i = 2^bitlen(p_i) × 0.11... (in base 2).
|
||
// And the product is:
|
||
// P = 2^todo × α
|
||
// where α is the product of nprimes numbers of the form 0.11...
|
||
//
|
||
// If α < 1/2 (which can happen for nprimes > 2), we need to
|
||
// shift todo to compensate for lost bits: the mean value of 0.11...
|
||
// is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2
|
||
// will give good results.
|
||
if nprimes >= 7 {
|
||
todo += (nprimes - 2) / 5
|
||
}
|
||
for i := 0; i < nprimes; i++ {
|
||
var err error
|
||
if len(prepopulatedPrimes) == 0 {
|
||
primes[i], err = rand.Prime(random, todo/(nprimes-i))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
} else {
|
||
primes[i] = prepopulatedPrimes[0]
|
||
prepopulatedPrimes = prepopulatedPrimes[1:]
|
||
}
|
||
|
||
todo -= primes[i].BitLen()
|
||
}
|
||
|
||
// Make sure that primes is pairwise unequal.
|
||
for i, prime := range primes {
|
||
for j := 0; j < i; j++ {
|
||
if prime.Cmp(primes[j]) == 0 {
|
||
continue NextSetOfPrimes
|
||
}
|
||
}
|
||
}
|
||
|
||
n := new(big.Int).Set(bigOne)
|
||
totient := new(big.Int).Set(bigOne)
|
||
pminus1 := new(big.Int)
|
||
for _, prime := range primes {
|
||
n.Mul(n, prime)
|
||
pminus1.Sub(prime, bigOne)
|
||
totient.Mul(totient, pminus1)
|
||
}
|
||
if n.BitLen() != bits {
|
||
// This should never happen for nprimes == 2 because
|
||
// crypto/rand should set the top two bits in each prime.
|
||
// For nprimes > 2 we hope it does not happen often.
|
||
continue NextSetOfPrimes
|
||
}
|
||
|
||
priv.D = new(big.Int)
|
||
e := big.NewInt(int64(priv.E))
|
||
ok := priv.D.ModInverse(e, totient)
|
||
|
||
if ok != nil {
|
||
priv.Primes = primes
|
||
priv.N = n
|
||
break
|
||
}
|
||
}
|
||
|
||
priv.Precompute()
|
||
return priv, nil
|
||
}
|