From db6d4406059cf8e61876ac1bedbb0405e333bda3 Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Sun, 10 Mar 2024 01:17:52 +0000 Subject: [PATCH 1/5] Added the function `NewEntityFromKey` that allows passing precreated RSA or ECDSA keys. --- openpgp/key_wrap.go | 102 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 openpgp/key_wrap.go diff --git a/openpgp/key_wrap.go b/openpgp/key_wrap.go new file mode 100644 index 00000000..bfe52e19 --- /dev/null +++ b/openpgp/key_wrap.go @@ -0,0 +1,102 @@ +package openpgp + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + + "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" + "github.com/ProtonMail/go-crypto/openpgp/packet" + + internalecdsa "github.com/ProtonMail/go-crypto/openpgp/ecdsa" +) + +// NewEntity returns an Entity that contains either a RSA or ECDSA keypair +// passed by the user 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. It is not +// required to assign any of the key type parameters in the config (in fact, +// they will be ignored); these will be set based on the passed key. +func NewEntityFromKey(name, comment, email string, key crypto.PrivateKey, config *packet.Config) (*Entity, error) { + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + + primaryPrivRaw, err := newSignerFromKey(key, config) + if err != nil { + return nil, err + } + primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw) + if config.V6() { + primary.UpgradeToV6() + } + + e := &Entity{ + PrimaryKey: &primary.PublicKey, + PrivateKey: primary, + Identities: make(map[string]*Identity), + Subkeys: []Subkey{}, + Signatures: []*packet.Signature{}, + } + + if config.V6() { + // In v6 keys algorithm preferences should be stored in direct key signatures + selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypeDirectSignature, config) + err = writeKeyProperties(selfSignature, creationTime, keyLifetimeSecs, config) + if err != nil { + return nil, err + } + err = selfSignature.SignDirectKeyBinding(&primary.PublicKey, primary, config) + if err != nil { + return nil, err + } + e.Signatures = append(e.Signatures, selfSignature) + e.SelfSignature = selfSignature + } + + err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs, !config.V6()) + 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 newSignerFromKey(key crypto.PrivateKey, config *packet.Config) (interface{}, error) { + switch key := key.(type) { + case *rsa.PrivateKey: + config.Algorithm = packet.PubKeyAlgoRSA + return key, nil + case *ecdsa.PrivateKey: + var c ecc.ECDSACurve + switch key.Curve { + case elliptic.P256(): + c = ecc.NewGenericCurve(elliptic.P256()) + config.Curve = packet.CurveNistP256 + case elliptic.P384(): + c = ecc.NewGenericCurve(elliptic.P384()) + config.Curve = packet.CurveNistP384 + case elliptic.P521(): + c = ecc.NewGenericCurve(elliptic.P521()) + config.Curve = packet.CurveNistP521 + default: + return nil, errors.InvalidArgumentError("unsupported elliptic curve") + } + priv := internalecdsa.NewPrivateKey( + *internalecdsa.NewPublicKey(c), + ) + priv.PublicKey.X, priv.PublicKey.Y, priv.D = key.X, key.Y, key.D + config.Algorithm = packet.PubKeyAlgoECDSA + return priv, nil + default: + return nil, errors.InvalidArgumentError("unsupported public key algorithm") + } +} From 2eb6c1f7e2b56090ebad145038c0515b7843f16d Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Sun, 10 Mar 2024 01:29:49 +0000 Subject: [PATCH 2/5] Added support to `crypto/ed25519` too. --- openpgp/key_wrap.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpgp/key_wrap.go b/openpgp/key_wrap.go index bfe52e19..a72646f7 100644 --- a/openpgp/key_wrap.go +++ b/openpgp/key_wrap.go @@ -3,6 +3,7 @@ package openpgp import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rsa" @@ -11,6 +12,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" internalecdsa "github.com/ProtonMail/go-crypto/openpgp/ecdsa" + internaled25519 "github.com/ProtonMail/go-crypto/openpgp/ed25519" ) // NewEntity returns an Entity that contains either a RSA or ECDSA keypair @@ -19,6 +21,10 @@ import ( // "()<>\x00". If config is nil, sensible defaults will be used. It is not // required to assign any of the key type parameters in the config (in fact, // they will be ignored); these will be set based on the passed key. +// +// The following key types are currently supported: *rsa.PrivateKey, +// *ecdsa.PrivateKey and ed25519.PrivateKey (not a pointer). +// Unsupported key types result in an error. func NewEntityFromKey(name, comment, email string, key crypto.PrivateKey, config *packet.Config) (*Entity, error) { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() @@ -96,6 +102,13 @@ func newSignerFromKey(key crypto.PrivateKey, config *packet.Config) (interface{} priv.PublicKey.X, priv.PublicKey.Y, priv.D = key.X, key.Y, key.D config.Algorithm = packet.PubKeyAlgoECDSA return priv, nil + case ed25519.PrivateKey: + priv := internaled25519.NewPrivateKey( + *internaled25519.NewPublicKey(), + ) + priv.Key = key.Seed() + config.Algorithm = packet.PubKeyAlgoEd25519 + return priv, nil default: return nil, errors.InvalidArgumentError("unsupported public key algorithm") } From 4a3ea54d5ef02e2b94e92f5fe4ee24be02163ea3 Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Sun, 10 Mar 2024 01:36:15 +0000 Subject: [PATCH 3/5] Fixed the comment adding support to Ed25519 too. --- openpgp/key_wrap.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp/key_wrap.go b/openpgp/key_wrap.go index a72646f7..96a8ff40 100644 --- a/openpgp/key_wrap.go +++ b/openpgp/key_wrap.go @@ -15,10 +15,10 @@ import ( internaled25519 "github.com/ProtonMail/go-crypto/openpgp/ed25519" ) -// NewEntity returns an Entity that contains either a RSA or ECDSA keypair -// passed by the user 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. It is not +// NewEntity returns an Entity that contains either a RSA, ECDSA or Ed25519 +// keypair passed by the user 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. It is not // required to assign any of the key type parameters in the config (in fact, // they will be ignored); these will be set based on the passed key. // From 497e4b2688fa687483ace3034bd3fc5461f2bc8f Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Sun, 10 Mar 2024 02:01:43 +0000 Subject: [PATCH 4/5] Assigning also the `DefaultHash` to correspond to the ECDSA key size. --- openpgp/key_wrap.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpgp/key_wrap.go b/openpgp/key_wrap.go index 96a8ff40..53152c7b 100644 --- a/openpgp/key_wrap.go +++ b/openpgp/key_wrap.go @@ -87,12 +87,19 @@ func newSignerFromKey(key crypto.PrivateKey, config *packet.Config) (interface{} case elliptic.P256(): c = ecc.NewGenericCurve(elliptic.P256()) config.Curve = packet.CurveNistP256 + // The default hash SHA256 will serve here case elliptic.P384(): c = ecc.NewGenericCurve(elliptic.P384()) config.Curve = packet.CurveNistP384 + if config.DefaultHash == 0 { + config.DefaultHash = crypto.SHA384 + } case elliptic.P521(): c = ecc.NewGenericCurve(elliptic.P521()) config.Curve = packet.CurveNistP521 + if config.DefaultHash == 0 { + config.DefaultHash = crypto.SHA512 + } default: return nil, errors.InvalidArgumentError("unsupported elliptic curve") } From 3f24ede7d362a34ec74c502d690b6a08f8dd25eb Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Mon, 11 Mar 2024 02:39:08 +0000 Subject: [PATCH 5/5] * Fixed wrapping of `crypto/ed25519` keys. * Added function `AddSigningSubkeyFromKey`. --- openpgp/key_wrap.go | 81 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/openpgp/key_wrap.go b/openpgp/key_wrap.go index 53152c7b..977bb1e7 100644 --- a/openpgp/key_wrap.go +++ b/openpgp/key_wrap.go @@ -7,25 +7,25 @@ import ( "crypto/elliptic" "crypto/rsa" + "github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/internal/ecc" "github.com/ProtonMail/go-crypto/openpgp/packet" internalecdsa "github.com/ProtonMail/go-crypto/openpgp/ecdsa" - internaled25519 "github.com/ProtonMail/go-crypto/openpgp/ed25519" ) -// NewEntity returns an Entity that contains either a RSA, ECDSA or Ed25519 -// keypair passed by the user 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. It is not -// required to assign any of the key type parameters in the config (in fact, -// they will be ignored); these will be set based on the passed key. +// NewSigningEntityFromKey returns an Entity that contains either a RSA, ECDSA +// or Ed25519 keypair passed by the user 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. +// It is not required to assign any of the key type parameters in the config +// (in fact, they will be ignored); these will be set based on the passed key. // // The following key types are currently supported: *rsa.PrivateKey, -// *ecdsa.PrivateKey and ed25519.PrivateKey (not a pointer). -// Unsupported key types result in an error. -func NewEntityFromKey(name, comment, email string, key crypto.PrivateKey, config *packet.Config) (*Entity, error) { +// *ecdsa.PrivateKey and ed25519.PrivateKey (not a pointer). Unsupported key +// types result in an error. +func NewSigningEntityFromKey(name, comment, email string, key crypto.PrivateKey, config *packet.Config) (*Entity, error) { creationTime := config.Now() keyLifetimeSecs := config.KeyLifetime() @@ -66,14 +66,47 @@ func NewEntityFromKey(name, comment, email string, key crypto.PrivateKey, config 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) + return e, nil +} + +func (e *Entity) AddSigningSubkeyFromKey(key crypto.PrivateKey, config *packet.Config) error { + creationTime := config.Now() + keyLifetimeSecs := config.KeyLifetime() + + subPrivRaw, err := newSignerFromKey(key, config) if err != nil { - return nil, err + return err + } + sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) + sub.IsSubkey = true + if config.V6() { + sub.UpgradeToV6() } - return e, nil + subkey := Subkey{ + PublicKey: &sub.PublicKey, + PrivateKey: sub, + } + subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config) + subkey.Sig.CreationTime = creationTime + subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs + subkey.Sig.FlagsValid = true + subkey.Sig.FlagSign = true + subkey.Sig.EmbeddedSignature = createSignaturePacket(subkey.PublicKey, packet.SigTypePrimaryKeyBinding, config) + subkey.Sig.EmbeddedSignature.CreationTime = creationTime + + err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config) + if err != nil { + return err + } + + err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config) + if err != nil { + return err + } + + e.Subkeys = append(e.Subkeys, subkey) + return nil } func newSignerFromKey(key crypto.PrivateKey, config *packet.Config) (interface{}, error) { @@ -110,11 +143,19 @@ func newSignerFromKey(key crypto.PrivateKey, config *packet.Config) (interface{} config.Algorithm = packet.PubKeyAlgoECDSA return priv, nil case ed25519.PrivateKey: - priv := internaled25519.NewPrivateKey( - *internaled25519.NewPublicKey(), - ) - priv.Key = key.Seed() - config.Algorithm = packet.PubKeyAlgoEd25519 + if config.V6() { + // Implementations MUST NOT accept or generate v6 key material + // using the deprecated OIDs. + return nil, errors.InvalidArgumentError("EdDSALegacy cannot be used for v6 keys") + } + curve := ecc.FindEdDSAByGenName(string(packet.Curve25519)) + if curve == nil { + return nil, errors.InvalidArgumentError("unsupported curve") + } + priv := eddsa.NewPrivateKey(*eddsa.NewPublicKey(curve)) + priv.PublicKey.X = key.Public().(ed25519.PublicKey)[:] + priv.D = key.Seed() + config.Algorithm = packet.PubKeyAlgoEdDSA return priv, nil default: return nil, errors.InvalidArgumentError("unsupported public key algorithm")