github.com/kubri/kubri@v0.5.1-0.20240317001612-bda2aaef967e/pkg/crypto/pgp/pgp.go (about) 1 package pgp 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 8 pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" 9 "github.com/ProtonMail/gopenpgp/v2/armor" 10 "github.com/ProtonMail/gopenpgp/v2/constants" 11 pgpcrypto "github.com/ProtonMail/gopenpgp/v2/crypto" 12 13 "github.com/kubri/kubri/pkg/crypto" 14 ) 15 16 type ( 17 PrivateKey = pgpcrypto.Key 18 PublicKey = pgpcrypto.Key 19 ) 20 21 // NewPrivateKey returns a new private key. 22 func NewPrivateKey(name, email string) (*PrivateKey, error) { 23 return pgpcrypto.GenerateKey(name, email, "x25519", 0) 24 } 25 26 // MarshalPrivateKey returns the armored private key. 27 func MarshalPrivateKey(key *PrivateKey) ([]byte, error) { 28 if key == nil { 29 return nil, crypto.ErrInvalidKey 30 } 31 if !key.IsPrivate() { 32 return nil, crypto.ErrWrongKeyType 33 } 34 s, err := key.ArmorWithCustomHeaders("", "") 35 if err != nil { 36 return nil, err 37 } 38 return []byte(s), nil 39 } 40 41 // UnmarshalPrivateKey returns a private key from an armored key. 42 func UnmarshalPrivateKey(b []byte) (*PrivateKey, error) { 43 key, err := pgpcrypto.NewKeyFromArmoredReader(bytes.NewReader(b)) 44 if err != nil { 45 return nil, wrapError(crypto.ErrInvalidKey, err) 46 } 47 if !key.IsPrivate() { 48 return nil, fmt.Errorf("%w: public key supplied instead of private key", crypto.ErrInvalidKey) 49 } 50 return key, nil 51 } 52 53 // Public extracts the public key from a private key. 54 func Public(key *PrivateKey) *PublicKey { 55 pub, err := key.ToPublic() 56 if err != nil { 57 return key 58 } 59 return pub 60 } 61 62 // MarshalPublicKey returns the armored public key. 63 func MarshalPublicKey(key *PublicKey) ([]byte, error) { 64 if key == nil { 65 return nil, crypto.ErrInvalidKey 66 } 67 if key.IsPrivate() { 68 return nil, crypto.ErrWrongKeyType 69 } 70 s, err := key.GetArmoredPublicKeyWithCustomHeaders("", "") 71 if err != nil { 72 return nil, err 73 } 74 return []byte(s), nil 75 } 76 77 // UnmarshalPublicKey returns a public key from an armored key. 78 func UnmarshalPublicKey(b []byte) (*PublicKey, error) { 79 key, err := pgpcrypto.NewKeyFromArmoredReader(bytes.NewReader(b)) 80 if err != nil { 81 return nil, wrapError(crypto.ErrInvalidKey, err) 82 } 83 if key.IsPrivate() { 84 return nil, fmt.Errorf("%w: private key supplied instead of public key", crypto.ErrInvalidKey) 85 } 86 return key, nil 87 } 88 89 // Sign signs the data with the private key. 90 func Sign(key *PrivateKey, data []byte) ([]byte, error) { 91 return sign(key, data, false) 92 } 93 94 // SignText signs the data with the private key and wraps it in a signed message. 95 // Data is considered text and canonicalised with CRLF line endings. 96 func SignText(key *PrivateKey, data []byte) ([]byte, error) { 97 data = bytes.ReplaceAll(data, lf, crlf) 98 sig, err := sign(key, data, true) 99 if err != nil { 100 return nil, err 101 } 102 b := make([]byte, 0, len(startText)+len(data)+len("\r\n")+len(sig)) 103 b = append(b, startText...) 104 b = append(b, data...) 105 b = append(b, "\r\n"...) 106 b = append(b, sig...) 107 return b, nil 108 } 109 110 func sign(key *PrivateKey, data []byte, text bool) ([]byte, error) { 111 if key == nil { 112 return nil, crypto.ErrInvalidKey 113 } 114 if !key.IsPrivate() { 115 return nil, crypto.ErrWrongKeyType 116 } 117 118 // TODO: Unlock locked key using env var passphrase. 119 120 keyring, err := pgpcrypto.NewKeyRing(key) 121 if err != nil { 122 return nil, err 123 } 124 125 msg := pgpcrypto.NewPlainMessage(data) 126 msg.TextType = text 127 128 signature, err := keyring.SignDetached(msg) 129 if err != nil { 130 return nil, err 131 } 132 133 sig, err := armor.ArmorWithTypeAndCustomHeaders(signature.Data, constants.PGPSignatureHeader, "", "") 134 if err != nil { 135 return nil, err 136 } 137 138 return []byte(sig), nil 139 } 140 141 // Verify verifies the signature of the data with the public key. 142 func Verify(key *PublicKey, data, sig []byte) bool { 143 signature, err := pgpcrypto.NewPGPSignatureFromArmored(string(sig)) 144 if err != nil { 145 return false 146 } 147 148 // TODO: Unlock locked key using env var passphrase. 149 150 keyring, err := pgpcrypto.NewKeyRing(key) 151 if err != nil { 152 return false 153 } 154 155 msg := pgpcrypto.NewPlainMessage(data) 156 157 err = keyring.VerifyDetached(msg, signature, pgpcrypto.GetUnixTime()) 158 return err == nil 159 } 160 161 // Split splits a signed message into data and signature. 162 func Split(msg []byte) (data, sig []byte, _ error) { 163 start := bytes.Index(msg, startText) 164 end := bytes.Index(msg, endText) 165 166 if start == -1 || end == -1 { 167 return nil, nil, ErrInvalidMessage 168 } 169 170 return bytes.ReplaceAll(msg[start+len(startText):end], crlf, lf), msg[end+2:], nil 171 } 172 173 //nolint:gochecknoglobals 174 var ( 175 startText = []byte("-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: SHA512\r\n\r\n") 176 endText = []byte("\r\n-----BEGIN PGP SIGNATURE-----") 177 lf = []byte("\n") 178 crlf = []byte("\r\n") 179 ) 180 181 var ErrInvalidMessage = errors.New("pgp: invalid message") 182 183 func wrapError(wrapErr, err error) error { 184 var e pgperrors.InvalidArgumentError 185 if errors.As(err, &e) { 186 return fmt.Errorf("%w: %s", wrapErr, string(e)) 187 } 188 return err 189 }