a32c9984cdc6578a1eb5a4bc0b6ee9717d0b3c19
[epoint] / pkg / key / key.go
1 // Package key implements epoint key pair generation and handling.
2 //
3 // An epoint key is an OpenPGP signing key that contains a self-signed
4 // user id packet which matches
5 //     "Issuer (<denomination>)"
6 // or
7 //     "Holder of <issuer fpr> (<denomination>)"
8 //
9 // The OpenPGP DSA key material is generated from a random seed using
10 // a deterministic algorithm. (The self-signature is not deterministic
11 // but the key material and thus the fingerprint is.)
12 // This makes it possible to represent an obligation issuer or holder key
13 // pair with a few bits of secret random seed.
14 // (The user id only needs to be set up correctly when the key is uploaded
15 // to the epoint server, it is not required for signing draft documents.)
16 package key
17
18 import (
19         "bytes"
20         "crypto"
21         "crypto/dsa"
22         "crypto/openpgp"
23         "crypto/openpgp/armor"
24         "crypto/openpgp/packet"
25         "crypto/rand"
26         "crypto/sha1"
27         "fmt"
28         "io"
29         "math/big"
30         "time"
31 )
32
33 // TODO: keep denomination only in issuer key?
34 // TODO: cleanup
35 // TODO: server key
36
37 const P = "A4D2B9575C25F0E622B8694387128A793E1AD27D12FFF4B5BA11A37CEFD31C935BCBB0A944581A6E6DA12986FCBA9D666607D71D365C286B9BCB57F6D938BE74982B7D770CE438F03B0A20ABA02E5691458C39D96E6E86AE564176ED1A6DFBAFB6EE7674CC5EDCF9FEB6158471FB3FAB53BA1CE1BA64C5626B9E8585FCEF5D31"
38 const Q = "FFFFFFFFFFFFFFFFFFFF254EAF9E7916D607AAAF"
39 const G = "7EA5C898777BE4BB29DCDC47289E718F7274C9CD7E570D3D552F3B3EE43C3DEF7BA68E57786926520CCAC71DBA13F37C4064395D5AF3334A04ABD8CED5E7FF476C661953936E8ADDE96A39D8C4AC1080A2BE3FE863A24B08BD43827E54AFADA72433704EA3C12E50E5BD08C130C68A1402FC20DA79CFE0DE931C414348D32B10"
40
41 // Calculate DSA private key from given random seed r
42 func DsaKey(r []byte) *dsa.PrivateKey {
43         priv := new(dsa.PrivateKey)
44         priv.Parameters.P, _ = new(big.Int).SetString(P, 16)
45         priv.Parameters.Q, _ = new(big.Int).SetString(Q, 16)
46         priv.Parameters.G, _ = new(big.Int).SetString(G, 16)
47
48         x := new(big.Int)
49 loop:
50         h := sha1.New()
51         h.Write(r)
52         r = h.Sum(nil)
53         x.SetBytes(r)
54         // TODO: zero out r and h ?
55         if x.Sign() == 0 || x.Cmp(priv.Q) >= 0 {
56                 // very rare
57                 goto loop
58         }
59         priv.X = x
60         priv.Y = new(big.Int)
61         priv.Y.Exp(priv.G, x, priv.P)
62         return priv
63 }
64
65 // Generate a random DSA private key
66 func RandomDsaKey() (priv *dsa.PrivateKey, err error) {
67         r := make([]byte, sha1.Size)
68         _, err = io.ReadFull(rand.Reader, r)
69         priv = DsaKey(r)
70         return
71 }
72
73 // New returns an openpgp.Entity that contains a fresh DSA private key with a
74 // single identity composed of the given full name, comment and email, any of
75 // which may be empty but must not contain any of "()<>\x00".
76 func New(priv *dsa.PrivateKey, t time.Time, name, comment, email string) (e *openpgp.Entity, err error) {
77         uid := packet.NewUserId(name, comment, email)
78         if uid == nil {
79                 return nil, fmt.Errorf("NewEntity: invalid argument: user id field contained invalid characters")
80         }
81         e = &openpgp.Entity{
82                 PrimaryKey: packet.NewDSAPublicKey(t, &priv.PublicKey),
83                 PrivateKey: packet.NewDSAPrivateKey(t, priv),
84                 Identities: make(map[string]*openpgp.Identity),
85         }
86         isPrimaryId := true
87         e.Identities[uid.Id] = &openpgp.Identity{
88                 Name:   uid.Name,
89                 UserId: uid,
90                 SelfSignature: &packet.Signature{
91                         CreationTime: t,
92                         SigType:      packet.SigTypePositiveCert,
93                         PubKeyAlgo:   packet.PubKeyAlgoDSA,
94                         Hash:         crypto.SHA256,
95                         IsPrimaryId:  &isPrimaryId,
96                         FlagsValid:   true,
97                         FlagSign:     true,
98                         FlagCertify:  true,
99                         IssuerKeyId:  &e.PrimaryKey.KeyId,
100                 },
101         }
102         return
103 }
104
105 // Parse armored or binary openpgp public or private key
106 func Parse(d []byte) (e *openpgp.Entity, err error) {
107         elist, err := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d))
108         if err != nil {
109                 elist1, err1 := openpgp.ReadKeyRing(bytes.NewBuffer(d))
110                 if err1 != nil {
111                         return
112                 }
113                 err = nil
114                 elist = elist1
115         }
116         if len(elist) != 1 {
117                 err = fmt.Errorf("Parse: expected exactly one key")
118                 return
119         }
120         e = elist[0]
121         return
122 }
123
124 // Prepare self signatures of private key
125 func SelfSign(e *openpgp.Entity) (err error) {
126         // TODO: maybe e.Serialize should do this internally
127         if e.PrivateKey == nil {
128                 err = fmt.Errorf("SelfSign: not a private key")
129                 return
130         }
131         for _, ident := range e.Identities {
132                 err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
133                 if err != nil {
134                         return
135                 }
136         }
137         for _, subkey := range e.Subkeys {
138                 err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey)
139                 if err != nil {
140                         return
141                 }
142         }
143         return
144 }
145
146 // Format into an armored public key
147 func Format(e *openpgp.Entity) (d []byte, err error) {
148         b := new(bytes.Buffer)
149         w, err := armor.Encode(b, openpgp.PublicKeyType, nil)
150         if err != nil {
151                 return
152         }
153         err = e.Serialize(w)
154         if err != nil {
155                 return
156         }
157         err = w.Close()
158         if err != nil {
159                 return
160         }
161         _, err = b.Write([]byte{'\n'})
162         if err != nil {
163                 return
164         }
165         d = b.Bytes()
166         return
167 }
168
169 // Issuer generates a key for obligation issuer clients from random seed r
170 func Issuer(r []byte, denomination string) (e *openpgp.Entity, err error) {
171         return New(DsaKey(r), time.Unix(0, 0), "Issuer", denomination, "")
172 }
173
174 // Holder generates a key for obligation holder clients from random seed r
175 func Holder(r []byte, issuer, denomination string) (e *openpgp.Entity, err error) {
176         return New(DsaKey(r), time.Unix(0, 0), "Holder of "+issuer, denomination, "")
177 }
178
179 // Server generates a key for the server from random seed r
180 func Server(r []byte) (e *openpgp.Entity, err error) {
181         return New(DsaKey(r), time.Now(), "Server", "", "")
182 }
183
184 // Key id (fingerprint)
185 func Id(e *openpgp.Entity) string {
186         return fmt.Sprintf("%X", e.PrimaryKey.Fingerprint)
187 }
188
189 // Check the issuer and denomination associated with the given pgp key
190 func Check(e *openpgp.Entity) (isIssuer bool, issuer, denomination string, err error) {
191         // allow multiple identities, use the first one that looks like an epoint uid
192         for _, id := range e.Identities {
193                 denomination = id.UserId.Comment
194                 if id.UserId.Name == "Issuer" {
195                         isIssuer = true
196                         issuer = Id(e)
197                         return
198                 }
199                 const prefix = "Holder of "
200                 if id.UserId.Name[:len(prefix)] == prefix {
201                         issuer = id.UserId.Name[len(prefix):]
202                         return
203                 }
204         }
205         err = fmt.Errorf("Check: no valid userid was found")
206         return
207 }