1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"crypto"
8	"fmt"
9	"strings"
10
11	"github.com/keybase/go-crypto/openpgp"
12	"github.com/keybase/go-crypto/openpgp/errors"
13	"github.com/keybase/go-crypto/openpgp/packet"
14	"github.com/keybase/go-crypto/openpgp/s2k"
15	"github.com/keybase/go-crypto/rsa"
16)
17
18type PGPGenArg struct {
19	PrimaryBits     int
20	SubkeyBits      int
21	Ids             Identities
22	Config          *packet.Config
23	PGPUids         []string
24	PrimaryLifetime int
25	SubkeyLifetime  int
26}
27
28func ui32p(i int) *uint32 {
29	if i >= 0 {
30		tmp := uint32(i)
31		return &tmp
32	}
33	return nil
34}
35
36// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
37// single identity composed of the given full name, comment and email, any of
38// which may be empty but must not contain any of "()<>\x00".
39// If config is nil, sensible defaults will be used.
40//
41// Modification of: https://code.google.com/p/go/source/browse/openpgp/keys.go?repo=crypto&r=8fec09c61d5d66f460d227fd1df3473d7e015bc6#456
42//  From golang.com/x/crypto/openpgp/keys.go
43func GeneratePGPKeyBundle(g *GlobalContext, arg PGPGenArg, logUI LogUI) (*PGPKeyBundle, error) {
44	currentTime := arg.Config.Now()
45
46	if len(arg.Ids) == 0 {
47		return nil, errors.InvalidArgumentError("No Ids in PGPArg")
48	}
49	uids, err := arg.PGPUserIDs()
50	if err != nil {
51		return nil, err
52	}
53	for i, id := range arg.Ids {
54		extra := ""
55		if i == 0 {
56			extra = "[primary]"
57		}
58		if logUI != nil {
59			logUI.Info("PGP User ID: %s %s", id, extra)
60		}
61	}
62
63	if logUI != nil {
64		logUI.Info("Generating primary key (%d bits)", arg.PrimaryBits)
65	}
66	masterPriv, err := rsa.GenerateKey(arg.Config.Random(), arg.PrimaryBits)
67	if err != nil {
68		return nil, err
69	}
70
71	if logUI != nil {
72		logUI.Info("Generating encryption subkey (%d bits)", arg.SubkeyBits)
73	}
74	encryptingPriv, err := rsa.GenerateKey(arg.Config.Random(), arg.SubkeyBits)
75	if err != nil {
76		return nil, err
77	}
78
79	e := &openpgp.Entity{
80		PrimaryKey: packet.NewRSAPublicKey(currentTime, &masterPriv.PublicKey),
81		PrivateKey: packet.NewRSAPrivateKey(currentTime, masterPriv),
82		Identities: make(map[string]*openpgp.Identity),
83	}
84
85	for i, uid := range uids {
86		isPrimaryID := true
87		if i > 0 {
88			isPrimaryID = false
89		}
90		id := &openpgp.Identity{
91			Name:   uid.Name,
92			UserId: uid,
93			SelfSignature: &packet.Signature{
94				CreationTime:         currentTime,
95				SigType:              packet.SigTypePositiveCert,
96				PubKeyAlgo:           packet.PubKeyAlgoRSA,
97				Hash:                 arg.Config.Hash(),
98				IsPrimaryId:          &isPrimaryID,
99				FlagsValid:           true,
100				FlagSign:             true,
101				FlagCertify:          true,
102				IssuerKeyId:          &e.PrimaryKey.KeyId,
103				PreferredSymmetric:   arg.PreferredSymmetric(),
104				PreferredHash:        arg.PreferredHash(),
105				PreferredCompression: arg.PreferredCompression(),
106			},
107		}
108		id.SelfSignature.KeyLifetimeSecs = ui32p(arg.PrimaryLifetime)
109		e.Identities[uid.Id] = id
110	}
111
112	e.Subkeys = make([]openpgp.Subkey, 1)
113	e.Subkeys[0] = openpgp.Subkey{
114		PublicKey:  packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey),
115		PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv),
116		Sig: &packet.Signature{
117			CreationTime:              currentTime,
118			SigType:                   packet.SigTypeSubkeyBinding,
119			PubKeyAlgo:                packet.PubKeyAlgoRSA,
120			Hash:                      arg.Config.Hash(),
121			FlagsValid:                true,
122			FlagEncryptStorage:        true,
123			FlagEncryptCommunications: true,
124			IssuerKeyId:               &e.PrimaryKey.KeyId,
125			PreferredSymmetric:        arg.PreferredSymmetric(),
126			PreferredHash:             arg.PreferredHash(),
127			PreferredCompression:      arg.PreferredCompression(),
128		},
129	}
130	e.Subkeys[0].PublicKey.IsSubkey = true
131	e.Subkeys[0].PrivateKey.IsSubkey = true
132	e.Subkeys[0].Sig.KeyLifetimeSecs = ui32p(arg.SubkeyLifetime)
133
134	return NewGeneratedPGPKeyBundle(e), nil
135}
136
137// CreateIDs creates identities for KeyGenArg.Ids if none exist.
138// It uses PGPUids to determine the set of Ids.  It does not set the
139// default keybase.io uid.  AddDefaultUid() does that.
140func (a *PGPGenArg) CreatePGPIDs() error {
141	if len(a.Ids) > 0 {
142		return nil
143	}
144	for _, id := range a.PGPUids {
145		if !strings.Contains(id, "<") && CheckEmail.F(id) {
146			a.Ids = append(a.Ids, Identity{Email: id})
147			continue
148		}
149		parsed, err := ParseIdentity(id)
150		if err != nil {
151			return err
152		}
153		a.Ids = append(a.Ids, *parsed)
154	}
155	return nil
156}
157
158// Just for testing
159func (a *PGPGenArg) AddDefaultUID(g *GlobalContext) {
160	a.Ids = append(a.Ids, KeybaseIdentity(g, ""))
161}
162
163// Just for testing
164func (a *PGPGenArg) MakeAllIds(g *GlobalContext) error {
165	if err := a.CreatePGPIDs(); err != nil {
166		return err
167	}
168	a.AddDefaultUID(g)
169	return nil
170}
171
172func (a *PGPGenArg) PGPUserIDs() ([]*packet.UserId, error) {
173	uids := make([]*packet.UserId, len(a.Ids))
174	for i, id := range a.Ids {
175		uids[i] = id.ToPGPUserID()
176		if uids[i] == nil {
177			return nil, fmt.Errorf("Id[%d] failed to convert to PGPUserId (%+v)", i, id)
178		}
179	}
180	return uids, nil
181}
182
183func (a *PGPGenArg) Init() (err error) {
184	defBits := 4096
185	if a.PrimaryBits == 0 {
186		a.PrimaryBits = defBits
187	}
188	if a.SubkeyBits == 0 {
189		a.SubkeyBits = defBits
190	}
191	if a.PrimaryLifetime == 0 {
192		a.PrimaryLifetime = KeyExpireIn
193	}
194	if a.SubkeyLifetime == 0 {
195		a.SubkeyLifetime = SubkeyExpireIn
196	}
197	return
198}
199
200func (a *PGPGenArg) PreferredSymmetric() []uint8 {
201	return []uint8{
202		uint8(packet.CipherAES128),
203		uint8(packet.CipherAES256),
204		uint8(packet.CipherCAST5),
205	}
206}
207
208func (a *PGPGenArg) PreferredHash() []uint8 {
209	gohash := []crypto.Hash{
210		crypto.SHA256,
211		crypto.SHA512,
212		crypto.SHA1,
213		crypto.RIPEMD160,
214	}
215	var res []uint8
216	for _, h := range gohash {
217		id, ok := s2k.HashToHashId(h)
218		if !ok {
219			continue
220		}
221		res = append(res, id)
222	}
223	return res
224}
225
226func (a *PGPGenArg) PreferredCompression() []uint8 {
227	return []uint8{
228		uint8(packet.CompressionNone),
229		uint8(packet.CompressionZIP),
230		uint8(packet.CompressionZLIB),
231	}
232}
233