Skip to content

Commit

Permalink
Add helper functions to inspect and create key packets
Browse files Browse the repository at this point in the history
  • Loading branch information
lubux committed Jan 31, 2024
1 parent 02a4599 commit ee28f61
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ jobs:
go-version: ^1.16
id: go

- name: Install NDK
uses: nttld/setup-ndk@v1
with:
ndk-version: r23c
link-to-sdk: true

- name: Checkout
uses: actions/checkout@v2

Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.6.0] 2023-03-15

### Added
- API to get signature key IDs for mobile:
```go
func (msg *PGPMessage) GetHexSignatureKeyIDsJson() []byte
```
- API to get encryption key IDs for mobile:
```go
func (msg *PGPMessage) GetHexEncryptionKeyIDsJson() []byte
```
- API to get the number of key packets in a PGP message:
```go
func (msg *PGPSplitMessage) GetNumberOfKeyPackets() (int, error)
```
- API in package `helper` to encrypt a PGP message to an additional key:
```go
func EncryptPGPMessageToAdditionalKey(messageToModify *crypto.PGPSplitMessage, keyRing *crypto.KeyRing, additionalKey *crypto.KeyRing) error
```

## [2.7.4] 2023-10-27
### Fixed
- Ensure that `(SessionKey).Decrypt` functions return an error if no integrity protection is present in the encrypted input. To protect SEIPDv1 encrypted messages, SED packets must not be allowed in decryption.
Expand Down
2 changes: 1 addition & 1 deletion constants/armor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package constants

// Constants for armored data.
const (
ArmorHeaderVersion = "GopenPGP 2.7.4"
ArmorHeaderVersion = "GopenPGP 2.7.5"
ArmorHeaderComment = "https://gopenpgp.org"
PGPMessageHeader = "PGP MESSAGE"
PGPSignatureHeader = "PGP SIGNATURE"
Expand Down
2 changes: 1 addition & 1 deletion constants/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package constants

const Version = "2.7.4"
const Version = "2.7.5"
51 changes: 51 additions & 0 deletions crypto/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package crypto
import (
"bytes"
"encoding/base64"
"encoding/json"
goerrors "errors"
"io"
"io/ioutil"
Expand Down Expand Up @@ -287,6 +288,21 @@ func (msg *PGPMessage) GetHexEncryptionKeyIDs() ([]string, bool) {
return getHexKeyIDs(msg.GetEncryptionKeyIDs())
}

// GetHexEncryptionKeyIDsJson returns the key IDs of the keys to which the session key is encrypted as a JSON array.
// If an error occurs it returns nil.
// Helper function for go-mobile clients.
func (msg *PGPMessage) GetHexEncryptionKeyIDsJson() []byte {
hexIds, ok := msg.GetHexEncryptionKeyIDs()
if !ok {
return nil
}
hexIdsJson, err := json.Marshal(hexIds)
if err != nil {
return nil
}
return hexIdsJson
}

// GetSignatureKeyIDs Returns the key IDs of the keys to which the (readable) signature packets are encrypted to.
func (msg *PGPMessage) GetSignatureKeyIDs() ([]uint64, bool) {
return getSignatureKeyIDs(msg.Data)
Expand All @@ -297,6 +313,20 @@ func (msg *PGPMessage) GetHexSignatureKeyIDs() ([]string, bool) {
return getHexKeyIDs(msg.GetSignatureKeyIDs())
}

// GetHexSignatureKeyIDsJson returns the key IDs of the keys to which the (readable) signature packets
// are encrypted to as a JSON array. Helper function for go-mobile clients.
func (msg *PGPMessage) GetHexSignatureKeyIDsJson() []byte {
sigHexSigIds, ok := msg.GetHexSignatureKeyIDs()
if !ok {
return nil
}
sigHexKeyIdsJSON, err := json.Marshal(sigHexSigIds)
if err != nil {
return nil
}
return sigHexKeyIdsJSON
}

// GetBinaryDataPacket returns the unarmored binary datapacket as a []byte.
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
return msg.DataPacket
Expand Down Expand Up @@ -324,6 +354,27 @@ func (msg *PGPSplitMessage) GetPGPMessage() *PGPMessage {
return NewPGPMessage(append(msg.KeyPacket, msg.DataPacket...))
}

// GetNumberOfKeyPackets returns the number of keys packets in this message.
func (msg *PGPSplitMessage) GetNumberOfKeyPackets() (int, error) {
bytesReader := bytes.NewReader(msg.KeyPacket)
packets := packet.NewReader(bytesReader)
var keyPacketCount int
for {
p, err := packets.Next()
if goerrors.Is(err, io.EOF) {
break
}
if err != nil {
return 0, err
}
switch p.(type) {
case *packet.SymmetricKeyEncrypted, *packet.EncryptedKey:
keyPacketCount += 1
}
}
return keyPacketCount, nil
}

// SplitMessage splits the message into key and data packet(s).
// Parameters are for backwards compatibility and are unused.
func (msg *PGPMessage) SplitMessage() (*PGPSplitMessage, error) {
Expand Down
22 changes: 22 additions & 0 deletions helper/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package helper

import "github.com/ProtonMail/gopenpgp/v2/crypto"

// EncryptPGPMessageToAdditionalKey decrypts the session key of the input PGPSplitMessage with a private key in keyRing
// and encrypts it towards the additionalKeys by adding the additional key packets to the input PGPSplitMessage.
// If successful, new key packets are added to message.
// * messageToModify : The encrypted pgp message that should be modified
// * keyRing : The private keys to decrypt the session key in the messageToModify.
// * additionalKey : The public keys the message should be additionally encrypted to.
func EncryptPGPMessageToAdditionalKey(messageToModify *crypto.PGPSplitMessage, keyRing *crypto.KeyRing, additionalKey *crypto.KeyRing) error {
sessionKey, err := keyRing.DecryptSessionKey(messageToModify.KeyPacket)
if err != nil {
return err
}
additionalKeyPacket, err := additionalKey.EncryptSessionKey(sessionKey)
if err != nil {
return err
}
messageToModify.KeyPacket = append(messageToModify.KeyPacket, additionalKeyPacket...)
return nil
}
56 changes: 56 additions & 0 deletions helper/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package helper_test

import (
"testing"

"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gopenpgp/v2/helper"
"github.com/stretchr/testify/assert"
)

func TestEncryptPGPMessageToAdditionalKey(t *testing.T) {
keyA, err := crypto.GenerateKey("A", "a@a.a", "x25519", 0)
if err != nil {
t.Fatal("Expected no error when generating key, got:", err)
}

keyB, err := crypto.GenerateKey("B", "b@b.b", "x25519", 0)
if err != nil {
t.Fatal("Expected no error when generating key, got:", err)
}

keyRingA, err := crypto.NewKeyRing(keyA)
if err != nil {
t.Fatal("Expected no error when creating keyring, got:", err)
}
keyRingB, err := crypto.NewKeyRing(keyB)
if err != nil {
t.Fatal("Expected no error when creating keyring, got:", err)
}

message := crypto.NewPlainMessageFromString("plain text")
// Encrypt towards A
ciphertext, err := keyRingA.Encrypt(message, nil)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
ciphertextSplit, err := ciphertext.SplitMessage()
if err != nil {
t.Fatal("Expected no error when splitting message, got:", err)
}
// Also encrypt the message towards B
if err := helper.EncryptPGPMessageToAdditionalKey(ciphertextSplit, keyRingA, keyRingB); err != nil {
t.Fatal("Expected no error when modifying the message, got:", err)
}

// Test decrypt with B
decrypted, err := keyRingB.Decrypt(
ciphertextSplit.GetPGPMessage(),
nil,
0,
)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
}

0 comments on commit ee28f61

Please sign in to comment.