2021-01-09 11:24:40 +01:00
|
|
|
package openpgpops
|
2021-01-05 19:59:43 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto"
|
2021-01-09 11:24:40 +01:00
|
|
|
"errors"
|
2021-01-05 20:25:29 +01:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/crypto/openpgp"
|
2021-01-08 10:14:11 +01:00
|
|
|
"golang.org/x/crypto/openpgp/armor"
|
2021-01-05 20:25:29 +01:00
|
|
|
"golang.org/x/crypto/openpgp/packet"
|
|
|
|
)
|
|
|
|
|
2021-01-09 11:24:40 +01:00
|
|
|
const hoursInADay = 24
|
|
|
|
|
2021-01-05 19:59:43 +01:00
|
|
|
type OpenPGPRoot struct {
|
|
|
|
Name string
|
|
|
|
SecretKeyRing string
|
|
|
|
Identifier string
|
|
|
|
}
|
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days uint16) ([]byte, error) {
|
2021-01-05 20:25:29 +01:00
|
|
|
signingKey, err := r.findSigningKey(r.Identifier)
|
|
|
|
if err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not find a signing key matching %s: %w", r.Identifier, err)
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pubKeyRing, err := openpgp.ReadKeyRing(bytes.NewReader(pubKey))
|
|
|
|
if err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not read openpgpops keyring: %w", err)
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
output := bytes.NewBuffer([]byte{})
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
armorOutput, err := armor.Encode(output, "PGP PUBLIC KEY BLOCK", map[string]string{})
|
|
|
|
if err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not create ASCII armor wrapper for openpgpops output: %w", err)
|
2021-01-08 10:14:11 +01:00
|
|
|
}
|
2021-01-05 20:25:29 +01:00
|
|
|
|
|
|
|
for _, pe := range pubKeyRing {
|
2021-01-08 10:14:11 +01:00
|
|
|
log.Tracef("found %+v", pe.PrimaryKey.KeyIdString())
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
for _, i := range pe.Identities {
|
2021-01-08 10:14:11 +01:00
|
|
|
expiry := calculateExpiry(i, days)
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
if !i.SelfSignature.KeyExpired(time.Now()) {
|
2021-01-08 10:14:11 +01:00
|
|
|
sig := &packet.Signature{
|
|
|
|
SigType: packet.SigTypeGenericCert,
|
|
|
|
PubKeyAlgo: signingKey.PrivateKey.PubKeyAlgo,
|
|
|
|
Hash: algorithm,
|
|
|
|
CreationTime: time.Now(),
|
|
|
|
SigLifetimeSecs: expiry,
|
|
|
|
IssuerKeyId: &signingKey.PrivateKey.KeyId,
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
2021-01-08 10:14:11 +01:00
|
|
|
if err := sig.SignUserId(i.Name, pe.PrimaryKey, signingKey.PrivateKey, &packet.Config{
|
|
|
|
DefaultHash: algorithm,
|
|
|
|
}); err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not sign identity %s: %w", i.Name, err)
|
2021-01-08 10:14:11 +01:00
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
i.Signatures = append(i.Signatures, sig)
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
if err = pe.Serialize(armorOutput); err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
2021-01-09 11:24:40 +01:00
|
|
|
"could not write signed public key %s to output: %w",
|
2021-01-08 10:14:11 +01:00
|
|
|
pe.PrimaryKey.KeyIdString(),
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
2021-01-08 10:14:11 +01:00
|
|
|
|
|
|
|
if err = armorOutput.Close(); err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not close output stream: %w", err)
|
2021-01-08 10:14:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Tracef("signed public key\n%s", output.String())
|
|
|
|
|
|
|
|
return output.Bytes(), nil
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
func calculateExpiry(i *openpgp.Identity, days uint16) *uint32 {
|
|
|
|
maxExpiry := time.Second * time.Duration(*i.SelfSignature.KeyLifetimeSecs)
|
2021-01-09 11:24:40 +01:00
|
|
|
calcExpiry := time.Hour * hoursInADay * time.Duration(days)
|
2021-01-05 20:25:29 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
if calcExpiry > maxExpiry {
|
|
|
|
calcExpiry = maxExpiry
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
expirySeconds := uint32(calcExpiry.Seconds())
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-08 10:14:11 +01:00
|
|
|
return &expirySeconds
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *OpenPGPRoot) findSigningKey(identifier string) (*openpgp.Entity, error) {
|
|
|
|
keyring, err := os.Open(r.SecretKeyRing)
|
|
|
|
if err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not open secret keyring: %w", err)
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
defer func() { _ = keyring.Close() }()
|
|
|
|
|
|
|
|
el, err := openpgp.ReadKeyRing(keyring)
|
|
|
|
if err != nil {
|
2021-01-09 11:24:40 +01:00
|
|
|
return nil, fmt.Errorf("could not read keyring: %w", err)
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
for _, e := range el {
|
2021-01-08 10:14:11 +01:00
|
|
|
log.Tracef("found %s", e.PrimaryKey.KeyIdString())
|
2021-01-09 11:24:40 +01:00
|
|
|
|
2021-01-05 20:25:29 +01:00
|
|
|
for _, i := range e.Identities {
|
|
|
|
if i.UserId.Email == identifier && len(e.Revocations) == 0 && !i.SelfSignature.KeyExpired(time.Now()) {
|
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-09 11:24:40 +01:00
|
|
|
|
|
|
|
return nil, errors.New("no matching key found")
|
2021-01-05 20:25:29 +01:00
|
|
|
}
|
|
|
|
|
2021-01-05 19:59:43 +01:00
|
|
|
type OpenPGPProfile struct {
|
|
|
|
Name string
|
|
|
|
}
|