0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Golang】OpenPGP 公開鍵の Notation 情報を取得する

Last updated at Posted at 2025-06-20

Go 言語(以下 golang)で OpenPGP の公開鍵から、ノーテーション情報(注釈情報以下 Notation)を取り出したい。

Keyoxide 用の Proof を、公開鍵から golang で取得したかったのです。

Go 本家の crypto モジュールにある openpgp パッケージは廃止されたため、ProtonMail がフォーク&メンテしている go-crypto から、openpgp だけをスピンオフさせた GopenPGP を利用しています。

GopenPGP v3

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/ProtonMail/gopenpgp/v3/crypto"
)

func main(pathFilePubKey string) {
    // OpenPGP 互換の公開鍵。ASCII Armored, バイナリ形式どちらでもOK
    pathFilePubKey := "path/to/pgppubkey.asc"

    // 公開鍵ファイル読込
	f, err := os.Open(pathFilePubKey)
	panicOnErr(err)
	defer f.Close()

	// gopenpgp v3 で公開鍵パース
	pubKey, err := crypto.NewKeyFromReader(f)
	panicOnErr(err)

	fmt.Println("* Fingerprint:", pubKey.GetFingerprint())

	for _, val := range pubKey.GetEntity().Identities {
		fmt.Println("* Identity Name:", val.Name)
		sigs, err := val.Primary.PrimarySelfSignature(time.Now().Add(300*time.Second), nil)
		panicOnErr(err)

		fmt.Println("* Notations:")
		for _, notation := range sigs.Notations {
			// 自動生成されるsaltはスキップ
			if notation.Name == "salt@notations.openpgpjs.org" {
				fmt.Printf("- Key(Name): %s, Value: [random value]\n", notation.Name)
				continue
			}
			// 人間可読ならそのまま
			if notation.IsHumanReadable {
				fmt.Printf("- Key(Name): %s, Value: %s\n", notation.Name, notation.Value)
			} else {
				// そうでなければ16進数で表示
				fmt.Printf("- Key(Name): %s, Value: %X\n", notation.Name, notation.Value)
			}
		}
	}
}

func panicOnErr(err error) {
	if err != nil {
		panic(err)
	}
}

GopenPGP v3 は、GopenPGP v2 と互換がありません。

v3 は、今後のメンテナンス性を向上するため、仕様の見直しや大幅なリファクタリングが行われました。そのため使い勝手も、いささか v2 と異なります。

特に、構造体のプロパティ(struct のフィールド)の多くが private になったことで露出度が減り、セキュリティの懸念事項が少なくなったり、メンテナンスしやすくなった反面、以前はアクセスできていたプロパティにアクセスできなくなっています。

これから GopenPGP モジュールを扱うなら v3 を使うべきですが、以前から v2 を使っており v3 を利用できない事情がある場合は、下記 GopenPGP v2 のサンプルをご覧ください。

GopenPGP v2

上記 GopenPGP v3 と同じ内容を GopenPGP v2 で出力する例です。

v3 と v2 は互換がないとは言え、v3 のコードとほとんど同じなのは、v3 側が内部的に v2 を利用しており、こちらでは直接プロパティを参照しています。

package main

import (
	"fmt"
	"os"

	cryptoV2 "github.com/ProtonMail/gopenpgp/v2/crypto"
)


func main(pathFilePubKey string) {
    // OpenPGP 互換の公開鍵。ASCII Armored, バイナリ形式どちらでもOK
    pathFilePubKey := "path/to/pgppubkey.asc"

    // 公開鍵ファイル読込
	f, err := os.Open(pathFilePubKey)
	panicOnErr(err)
	defer f.Close()

	var pubKey *cryptoV2.Key

    // 鍵の形式を検知して gopenpgp v2 でパース
	if r, isArmored := IsPGPArmored(f); isArmored {
		pubKey, err = cryptoV2.NewKeyFromArmoredReader(r)
		panicOnErr(err)
	} else {
		pubKey, err = cryptoV2.NewKeyFromReader(f)
		panicOnErr(err)
	}

	fmt.Println("* Fingerprint:", pubKey.GetFingerprint())

	for _, val := range pubKey.GetEntity().Identities {
		fmt.Println("- Identity Name:", val.Name)

        fmt.Println("* Notations:")
		for _, notation := range val.SelfSignature.Notations {
			// 自動生成されるsaltはスキップ
			if notation.Name == "salt@notations.openpgpjs.org" {
				fmt.Printf("- Key(Name): %s, Value: [random value]\n", notation.Name)
				continue
			}

			// 可読ならそのまま、そうでなければ16進数で表示
			if notation.IsHumanReadable {
				fmt.Printf("- Key(Name): %s, Value: %s\n", notation.Name, notation.Value)
			} else {
				fmt.Printf("- Key(Name): %s, Value: %X\n", notation.Name, notation.Value)
			}
		}
	}
}

func panicOnErr(err error) {
	if err != nil {
		panic(err)
	}
}

// IsPGPArmored reads a prefix from the reader and checks if it is equal to a
// pgp armored message prefix.
//
// Returns an io.Reader that is reset to the state of the in reader, and a bool
// that indicates if there is a match. If reading from the reader fails, the
// returned bool is set to false.
//
// This function was taken from:
//   - https://github.com/ProtonMail/gopenpgp/blob/b138a82ed0527abd91d1252be35166e45babcfae/armor/armor.go#L175
//   - Copyright (c) 2019 Proton Technologies AG, MIT License
func IsPGPArmored(in io.Reader) (io.Reader, bool) {
	const armorPrefix = "-----BEGIN PGP"
	const maxGarbageBytes = 128

	buffer := make([]byte, len(armorPrefix)+maxGarbageBytes)
	n, _ := io.ReadFull(in, buffer)
	outReader := io.MultiReader(bytes.NewReader(buffer[:n]), in)
	if bytes.Contains(buffer[:n], []byte(armorPrefix)) {
		return outReader, true
	}
	return outReader, false
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?