// Package key implements epoint key pair generation and handling. // // An epoint key is an OpenPGP signing key that contains a self-signed // user id packet which matches // "Issuer ()" // or // "Holder of ()" // // The OpenPGP DSA key material is generated from a random seed using // a deterministic algorithm. (The self-signature is not deterministic // but the key material and thus the fingerprint is.) // This makes it possible to represent an obligation issuer or holder key // pair with a few bits of secret random seed. // (The user id only needs to be set up correctly when the key is uploaded // to the epoint server, it is not required for signing draft documents.) package key import ( "bytes" "crypto" "crypto/dsa" "crypto/openpgp" "crypto/openpgp/armor" "crypto/openpgp/packet" "crypto/rand" "crypto/sha1" "fmt" "io" "math/big" "time" ) // TODO: keep denomination only in issuer key? // TODO: cleanup // TODO: server key const P = "A4D2B9575C25F0E622B8694387128A793E1AD27D12FFF4B5BA11A37CEFD31C935BCBB0A944581A6E6DA12986FCBA9D666607D71D365C286B9BCB57F6D938BE74982B7D770CE438F03B0A20ABA02E5691458C39D96E6E86AE564176ED1A6DFBAFB6EE7674CC5EDCF9FEB6158471FB3FAB53BA1CE1BA64C5626B9E8585FCEF5D31" const Q = "FFFFFFFFFFFFFFFFFFFF254EAF9E7916D607AAAF" const G = "7EA5C898777BE4BB29DCDC47289E718F7274C9CD7E570D3D552F3B3EE43C3DEF7BA68E57786926520CCAC71DBA13F37C4064395D5AF3334A04ABD8CED5E7FF476C661953936E8ADDE96A39D8C4AC1080A2BE3FE863A24B08BD43827E54AFADA72433704EA3C12E50E5BD08C130C68A1402FC20DA79CFE0DE931C414348D32B10" // Calculate DSA private key from given random seed r func DsaKey(r []byte) *dsa.PrivateKey { priv := new(dsa.PrivateKey) priv.Parameters.P, _ = new(big.Int).SetString(P, 16) priv.Parameters.Q, _ = new(big.Int).SetString(Q, 16) priv.Parameters.G, _ = new(big.Int).SetString(G, 16) x := new(big.Int) loop: h := sha1.New() h.Write(r) r = h.Sum(nil) x.SetBytes(r) // TODO: zero out r and h ? if x.Sign() == 0 || x.Cmp(priv.Q) >= 0 { // very rare goto loop } priv.X = x priv.Y = new(big.Int) priv.Y.Exp(priv.G, x, priv.P) return priv } // Generate a random DSA private key func RandomDsaKey() (priv *dsa.PrivateKey, err error) { r := make([]byte, sha1.Size) _, err = io.ReadFull(rand.Reader, r) priv = DsaKey(r) return } // New returns an openpgp.Entity that contains a fresh DSA private key 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". func New(priv *dsa.PrivateKey, t time.Time, name, comment, email string) (e *openpgp.Entity, err error) { uid := packet.NewUserId(name, comment, email) if uid == nil { return nil, fmt.Errorf("NewEntity: invalid argument: user id field contained invalid characters") } e = &openpgp.Entity{ PrimaryKey: packet.NewDSAPublicKey(t, &priv.PublicKey), PrivateKey: packet.NewDSAPrivateKey(t, priv), Identities: make(map[string]*openpgp.Identity), } isPrimaryId := true e.Identities[uid.Id] = &openpgp.Identity{ Name: uid.Name, UserId: uid, SelfSignature: &packet.Signature{ CreationTime: t, SigType: packet.SigTypePositiveCert, PubKeyAlgo: packet.PubKeyAlgoDSA, Hash: crypto.SHA256, IsPrimaryId: &isPrimaryId, FlagsValid: true, FlagSign: true, FlagCertify: true, IssuerKeyId: &e.PrimaryKey.KeyId, }, } return } // Parse armored or binary openpgp public or private key func Parse(d []byte) (e *openpgp.Entity, err error) { elist, err := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d)) if err != nil { elist1, err1 := openpgp.ReadKeyRing(bytes.NewBuffer(d)) if err1 != nil { return } err = nil elist = elist1 } if len(elist) != 1 { err = fmt.Errorf("Parse: expected exactly one key") return } e = elist[0] return } // Prepare self signatures of private key func SelfSign(e *openpgp.Entity) (err error) { // TODO: maybe e.Serialize should do this internally if e.PrivateKey == nil { err = fmt.Errorf("SelfSign: not a private key") return } for _, ident := range e.Identities { err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey) if err != nil { return } } for _, subkey := range e.Subkeys { err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey) if err != nil { return } } return } // Format into an armored public key func Format(e *openpgp.Entity) (d []byte, err error) { b := new(bytes.Buffer) w, err := armor.Encode(b, openpgp.PublicKeyType, nil) if err != nil { return } err = e.Serialize(w) if err != nil { return } err = w.Close() if err != nil { return } _, err = b.Write([]byte{'\n'}) if err != nil { return } d = b.Bytes() return } // Issuer generates a key for obligation issuer clients from random seed r func Issuer(r []byte, denomination string) (e *openpgp.Entity, err error) { return New(DsaKey(r), time.Unix(0, 0), "Issuer", denomination, "") } // Holder generates a key for obligation holder clients from random seed r func Holder(r []byte, issuer, denomination string) (e *openpgp.Entity, err error) { return New(DsaKey(r), time.Unix(0, 0), "Holder of "+issuer, denomination, "") } // Server generates a key for the server from random seed r func Server(r []byte) (e *openpgp.Entity, err error) { return New(DsaKey(r), time.Now(), "Server", "", "") } // Key id (fingerprint) func Id(e *openpgp.Entity) string { return fmt.Sprintf("%X", e.PrimaryKey.Fingerprint) } // Check the issuer and denomination associated with the given pgp key func Check(e *openpgp.Entity) (isIssuer bool, issuer, denomination string, err error) { // allow multiple identities, use the first one that looks like an epoint uid for _, id := range e.Identities { denomination = id.UserId.Comment if id.UserId.Name == "Issuer" { isIssuer = true issuer = Id(e) return } const prefix = "Holder of " if id.UserId.Name[:len(prefix)] == prefix { issuer = id.UserId.Name[len(prefix):] return } } err = fmt.Errorf("Check: no valid userid was found") return }