Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Chandler <wchandler@gitlab.com>2023-08-18 17:31:49 +0300
committerWill Chandler <wchandler@gitlab.com>2023-08-18 17:31:49 +0300
commit46892cb4fa5b0e86b7e86fbb7acda5effac412d6 (patch)
tree4c0fbf5294b2d20155c0f55c699e2f3ec7841164
parentc194a4ee2f1f14dfd1dec39c9ff4a10a7fd07c4b (diff)
parentb87326c283c89bf407a85b5d0b4364eb124a448a (diff)
Merge branch 'id-signing-key-rotation' into 'master'
Support key rotation for signing keys See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/6163 Merged-by: Will Chandler <wchandler@gitlab.com> Approved-by: Will Chandler <wchandler@gitlab.com> Reviewed-by: Igor Drozdov <idrozdov@gitlab.com> Reviewed-by: Will Chandler <wchandler@gitlab.com> Co-authored-by: Igor Drozdov <idrozdov@gitlab.com>
-rw-r--r--cmd/gitaly-git2go/git2goutil/sign.go4
-rw-r--r--cmd/gitaly-gpg/main.go4
-rw-r--r--internal/gitaly/config/config.go1
-rw-r--r--internal/gitaly/config/config_test.go4
-rw-r--r--internal/gitaly/service/commit/commit_signatures.go8
-rw-r--r--internal/gitaly/service/commit/commit_signatures_test.go50
-rw-r--r--internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519.sig6
-rw-r--r--internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519_sha256.sig6
-rw-r--r--internal/gitaly/service/commit/testdata/signing_ssh_key_rsa39
-rw-r--r--internal/gitaly/service/commit/testdata/signing_ssh_key_rsa.sig20
-rw-r--r--internal/gitaly/service/commit/testdata/signing_ssh_key_rsa_sha256.sig20
-rw-r--r--internal/gitaly/service/operations/cherry_pick_test.go2
-rw-r--r--internal/gitaly/service/operations/revert_test.go2
-rw-r--r--internal/gitaly/service/operations/squash_test.go2
-rw-r--r--internal/signature/signature.go59
-rw-r--r--internal/signature/signature_test.go41
-rw-r--r--internal/signature/testdata/signing_key.gpgbin0 -> 464 bytes
-rw-r--r--internal/signature/testdata/signing_key.gpg.sig7
-rw-r--r--internal/signature/testdata/signing_key.ssh8
-rw-r--r--internal/signature/testdata/signing_key.ssh.sig6
20 files changed, 260 insertions, 29 deletions
diff --git a/cmd/gitaly-git2go/git2goutil/sign.go b/cmd/gitaly-git2go/git2goutil/sign.go
index 596542706..efa8d1b1d 100644
--- a/cmd/gitaly-git2go/git2goutil/sign.go
+++ b/cmd/gitaly-git2go/git2goutil/sign.go
@@ -13,10 +13,10 @@ func CreateCommitSignature(signingKeyPath string, contentToSign []byte) ([]byte,
return nil, nil
}
- signingKey, err := signature.ParseSigningKey(signingKeyPath)
+ signingKeys, err := signature.ParseSigningKeys(signingKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to parse signing key: %w", err)
}
- return signingKey.CreateSignature(contentToSign)
+ return signingKeys.CreateSignature(contentToSign)
}
diff --git a/cmd/gitaly-gpg/main.go b/cmd/gitaly-gpg/main.go
index 1c1b5b550..ca41ed1d2 100644
--- a/cmd/gitaly-gpg/main.go
+++ b/cmd/gitaly-gpg/main.go
@@ -24,7 +24,7 @@ func gpgApp() *cli.App {
return errors.New("expected --status-fd=2")
}
- signingKey, err := signature.ParseSigningKey(cCtx.Args().First())
+ signingKeys, err := signature.ParseSigningKeys(cCtx.Args().First())
if err != nil {
return fmt.Errorf("reading signed key file %s : %w", cCtx.Args().First(), err)
}
@@ -34,7 +34,7 @@ func gpgApp() *cli.App {
return fmt.Errorf("reading contents from stdin: %w", err)
}
- sig, err := signingKey.CreateSignature(contents)
+ sig, err := signingKeys.CreateSignature(contents)
if err != nil {
return fmt.Errorf("creating signature: %w", err)
}
diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go
index 4c37b1c22..5b02d15bf 100644
--- a/internal/gitaly/config/config.go
+++ b/internal/gitaly/config/config.go
@@ -240,6 +240,7 @@ type Git struct {
CatfileCacheSize int `toml:"catfile_cache_size,omitempty" json:"catfile_cache_size"`
Config []GitConfig `toml:"config,omitempty" json:"config"`
SigningKey string `toml:"signing_key,omitempty" json:"signing_key"`
+ RotatedSigningKeys []string `toml:"rotated_signing_keys,omitempty" json:"rotated_signing_keys"`
}
// Validate runs validation on all fields and compose all found errors.
diff --git a/internal/gitaly/config/config_test.go b/internal/gitaly/config/config_test.go
index a1577eba9..47bdf7293 100644
--- a/internal/gitaly/config/config_test.go
+++ b/internal/gitaly/config/config_test.go
@@ -408,7 +408,8 @@ func TestLoadConfigCommand(t *testing.T) {
cmd := writeScript(t, `cat <<-EOF
{
"git": {
- "signing_key": "signing_key"
+ "signing_key": "signing_key",
+ "rotated_signing_keys": ["rotated_key_1", "rotated_key_2"]
}
}
EOF
@@ -425,6 +426,7 @@ func TestLoadConfigCommand(t *testing.T) {
cfg.ConfigCommand = cmd
cfg.Git.BinPath = "foo/bar"
cfg.Git.SigningKey = "signing_key"
+ cfg.Git.RotatedSigningKeys = []string{"rotated_key_1", "rotated_key_2"}
}),
}
},
diff --git a/internal/gitaly/service/commit/commit_signatures.go b/internal/gitaly/service/commit/commit_signatures.go
index 4ac24dd90..2aa9c957d 100644
--- a/internal/gitaly/service/commit/commit_signatures.go
+++ b/internal/gitaly/service/commit/commit_signatures.go
@@ -39,9 +39,9 @@ func (s *server) GetCommitSignatures(request *gitalypb.GetCommitSignaturesReques
}
defer cancel()
- var signingKey signature.SigningKey
+ var signingKeys *signature.SigningKeys
if s.cfg.Git.SigningKey != "" {
- signingKey, err = signature.ParseSigningKey(s.cfg.Git.SigningKey)
+ signingKeys, err = signature.ParseSigningKeys(s.cfg.Git.SigningKey, s.cfg.Git.RotatedSigningKeys...)
if err != nil {
return fmt.Errorf("failed to parse signing key: %w", err)
}
@@ -62,8 +62,8 @@ func (s *server) GetCommitSignatures(request *gitalypb.GetCommitSignaturesReques
}
signer := gitalypb.GetCommitSignaturesResponse_SIGNER_USER
- if signingKey != nil {
- if err := signingKey.Verify(signatureKey, commitText); err == nil {
+ if signingKeys != nil {
+ if err := signingKeys.Verify(signatureKey, commitText); err == nil {
signer = gitalypb.GetCommitSignaturesResponse_SIGNER_SYSTEM
}
}
diff --git a/internal/gitaly/service/commit/commit_signatures_test.go b/internal/gitaly/service/commit/commit_signatures_test.go
index 1a5e6671a..ba6d80ec6 100644
--- a/internal/gitaly/service/commit/commit_signatures_test.go
+++ b/internal/gitaly/service/commit/commit_signatures_test.go
@@ -60,6 +60,7 @@ func testGetCommitSignatures(t *testing.T, ctx context.Context) {
testcfg.BuildGitalyGPG(t, cfg)
cfg.Git.SigningKey = "testdata/signing_ssh_key_ed25519"
+ cfg.Git.RotatedSigningKeys = []string{"testdata/signing_ssh_key_rsa"}
cfg.SocketPath = startTestServices(t, cfg)
client := newCommitServiceClient(t, cfg.SocketPath)
@@ -302,11 +303,30 @@ func testGetCommitSignatures(t *testing.T, ctx context.Context) {
})
require.NoError(t, err)
+ rotatedKeyCommitID, err := repo.WriteCommit(ctx, localrepo.WriteCommitConfig{
+ TreeID: tree.OID,
+ AuthorName: gittest.DefaultCommitterName,
+ AuthorEmail: gittest.DefaultCommitterMail,
+ CommitterName: gittest.DefaultCommitterName,
+ CommitterEmail: gittest.DefaultCommitterMail,
+ AuthorDate: gittest.DefaultCommitTime,
+ CommitterDate: gittest.DefaultCommitTime,
+ Message: "rotated key commit message",
+ SigningKey: cfg.Git.RotatedSigningKeys[0],
+ })
+ require.NoError(t, err)
+
+ rsaSHA1Signature := string(testhelper.MustReadFile(t, "testdata/signing_ssh_key_rsa.sig"))
+ rsaSHA256Signature := string(testhelper.MustReadFile(t, "testdata/signing_ssh_key_rsa_sha256.sig"))
+ ed25519SHA1Signature := string(testhelper.MustReadFile(t, "testdata/signing_ssh_key_ed25519.sig"))
+ ed25519SHA256Signature := string(testhelper.MustReadFile(t, "testdata/signing_ssh_key_ed25519_sha256.sig"))
+
return setupData{
request: &gitalypb.GetCommitSignaturesRequest{
Repository: repoProto,
CommitIds: []string{
commitID.String(),
+ rotatedKeyCommitID.String(),
},
},
expectedResponses: testhelper.EnabledOrDisabledFlag(ctx, featureflag.GPGSigning,
@@ -314,20 +334,8 @@ func testGetCommitSignatures(t *testing.T, ctx context.Context) {
{
CommitId: commitID.String(),
Signature: []byte(gittest.ObjectHashDependent(t, map[string]string{
- "sha1": `-----BEGIN SSH SIGNATURE-----
-U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgVzKQNpRPvHihfJQJ+Com
-F8BdFuG2wuXh+LjXjbOs8IgAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3Nz
-aC1lZDI1NTE5AAAAQB6uCeUpvnFGR/cowe1pQyTZiTzKsi1tnez0EO8o2LtrJr+g
-k8fZo+m7jSM0TpefrL0iyHxevrbKslyXw1lJVAM=
------END SSH SIGNATURE-----
-`,
- "sha256": `-----BEGIN SSH SIGNATURE-----
-U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgVzKQNpRPvHihfJQJ+Com
-F8BdFuG2wuXh+LjXjbOs8IgAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3Nz
-aC1lZDI1NTE5AAAAQKgC1TFLVZOqvVs2AqCp2lhkRAUtZsDa89RgHOOsYAC3T1kB
-4lOayj2uzBahoM0gc7REITUyg5MTzfIhcIPfhAQ=
------END SSH SIGNATURE-----
-`,
+ "sha1": ed25519SHA1Signature,
+ "sha256": ed25519SHA256Signature,
})),
SignedText: []byte(fmt.Sprintf(
"tree %s\nauthor %s\ncommitter %s\n\nmessage",
@@ -337,6 +345,20 @@ aC1lZDI1NTE5AAAAQKgC1TFLVZOqvVs2AqCp2lhkRAUtZsDa89RgHOOsYAC3T1kB
)),
Signer: gitalypb.GetCommitSignaturesResponse_SIGNER_SYSTEM,
},
+ {
+ CommitId: rotatedKeyCommitID.String(),
+ Signature: []byte(gittest.ObjectHashDependent(t, map[string]string{
+ "sha1": rsaSHA1Signature,
+ "sha256": rsaSHA256Signature,
+ })),
+ SignedText: []byte(fmt.Sprintf(
+ "tree %s\nauthor %s\ncommitter %s\n\nrotated key commit message",
+ tree.OID,
+ gittest.DefaultCommitterSignature,
+ gittest.DefaultCommitterSignature,
+ )),
+ Signer: gitalypb.GetCommitSignaturesResponse_SIGNER_SYSTEM,
+ },
},
nil,
),
diff --git a/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519.sig b/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519.sig
new file mode 100644
index 000000000..31242342d
--- /dev/null
+++ b/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519.sig
@@ -0,0 +1,6 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgVzKQNpRPvHihfJQJ+Com
+F8BdFuG2wuXh+LjXjbOs8IgAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3Nz
+aC1lZDI1NTE5AAAAQB6uCeUpvnFGR/cowe1pQyTZiTzKsi1tnez0EO8o2LtrJr+g
+k8fZo+m7jSM0TpefrL0iyHxevrbKslyXw1lJVAM=
+-----END SSH SIGNATURE-----
diff --git a/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519_sha256.sig b/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519_sha256.sig
new file mode 100644
index 000000000..baa0da2e5
--- /dev/null
+++ b/internal/gitaly/service/commit/testdata/signing_ssh_key_ed25519_sha256.sig
@@ -0,0 +1,6 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgVzKQNpRPvHihfJQJ+Com
+F8BdFuG2wuXh+LjXjbOs8IgAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3Nz
+aC1lZDI1NTE5AAAAQKgC1TFLVZOqvVs2AqCp2lhkRAUtZsDa89RgHOOsYAC3T1kB
+4lOayj2uzBahoM0gc7REITUyg5MTzfIhcIPfhAQ=
+-----END SSH SIGNATURE-----
diff --git a/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa
new file mode 100644
index 000000000..09a40864d
--- /dev/null
+++ b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa
@@ -0,0 +1,39 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+AwQFBg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa.sig b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa.sig
new file mode 100644
index 000000000..ad5bed790
--- /dev/null
+++ b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa.sig
@@ -0,0 +1,20 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMbLvwoulsiF+dVI
+PrrSZxQSewl7nI57tIxvkaSY6xqhYFg10pF49lQS3uqGT9D9zEkAWR1UkcPD/QiB
+L/Mt1oDRhNhRoYbwpcFDyySV7a6IwJPX8oAo5t8EmNw8g6/nSEbzW66WdzZivFPn
+q0C4OSX5kDEVHG6jqXUQhwl/9c8+jc9KJRftP3klqqqoZsULrmxcEEHO/OwHQGub
+Px0WfuDVPtaRM3vx8Tpq7PHr2LV/a9mHFo9WylYBsuRoMZoXuQz1cRY39EqJ3gPM
+M04hbH2+SCRjl7oSw4oiplcek+X89C1WuV5V351X+QjLJfNxSpkDd5+S+JcpjV5S
+Ht3TmpMGvbDuEvG4IvKqBVDkthbULWtemRSBeGXgvjgUdXX22Mw+VMYx8TI20n0R
+5KeS8OzoiBN+0Vyb96FIDN43qPJg0C7G5JXeKXTE+zn6K+pBZhYtSIRHHDBwOmXo
+Q2s558M50VN0D1llY6hFtlZEvPp5uobwaEI7OgQQFbegWeFeuQAAAANnaXQAAAAA
+AAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgCgdx7uzvtHCshLxN/Ky
+h+Lvs7jTFlIiqxqZRE6hQKOI04qsOBZeARA3mFbGQKCRUc4Qb/vT1qDPHf6/CtFE
+kZ2TeK0gpEKmX29fzk2dpVvIKhtwBqUGFv7iRVI4NnyFtDOUwcjmCtlsIA6TD0X2
+8W+LpDt/Qm7H87yeviIxWegU2IiSuoG1euMvuTsRDquZAaOkkMmAtDn9/1vi9xtA
+Phkwl+T7sivXPX79ncE8bdP9eBu76WEzviOUo07h32c876FDU5RHfjfYBo3xya54
+LS+PiZjw2pTULVyOQWQIwR6cP8rjCH3mNI3ScbA8AYHHCzP38QHCzdcaOBQ4n9Yi
+q0XTOtXS2H67qkMC8Ge90nO+9833t7kOES1fsxGAVO7wfiUgnzT7DPzrbs9J43+P
+nn4helHH9Dy17WY+UzCzRkAOLOnZxU2VXPRcGUr7fshthA2Oa5mgHnDCP3sUw7hL
+WDKTjKjH4dHiw2BFbgpZuGmKkF9i1eSRLwGE8LZKHPWPcg==
+-----END SSH SIGNATURE-----
diff --git a/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa_sha256.sig b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa_sha256.sig
new file mode 100644
index 000000000..1c675bf5e
--- /dev/null
+++ b/internal/gitaly/service/commit/testdata/signing_ssh_key_rsa_sha256.sig
@@ -0,0 +1,20 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMbLvwoulsiF+dVI
+PrrSZxQSewl7nI57tIxvkaSY6xqhYFg10pF49lQS3uqGT9D9zEkAWR1UkcPD/QiB
+L/Mt1oDRhNhRoYbwpcFDyySV7a6IwJPX8oAo5t8EmNw8g6/nSEbzW66WdzZivFPn
+q0C4OSX5kDEVHG6jqXUQhwl/9c8+jc9KJRftP3klqqqoZsULrmxcEEHO/OwHQGub
+Px0WfuDVPtaRM3vx8Tpq7PHr2LV/a9mHFo9WylYBsuRoMZoXuQz1cRY39EqJ3gPM
+M04hbH2+SCRjl7oSw4oiplcek+X89C1WuV5V351X+QjLJfNxSpkDd5+S+JcpjV5S
+Ht3TmpMGvbDuEvG4IvKqBVDkthbULWtemRSBeGXgvjgUdXX22Mw+VMYx8TI20n0R
+5KeS8OzoiBN+0Vyb96FIDN43qPJg0C7G5JXeKXTE+zn6K+pBZhYtSIRHHDBwOmXo
+Q2s558M50VN0D1llY6hFtlZEvPp5uobwaEI7OgQQFbegWeFeuQAAAANnaXQAAAAA
+AAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgBYdJDU01OqZ/zVE6RfG
+fAShwbcouhB3E1ZBIC01gDGvEwN7h/TNFlSwlZThTt07CWatDaVvAYjhHj8Vcl5m
+bFbtsNxXhx+9ysL/RTy6fzi1LcHg4yEyYsOdumXn2/q5UipHgtlaar6GdfYO6M4a
+fhQUjkWg+rz7xB2wzjylklpTDtZK/Nspm1ikbXWer6+B158AMt+6xFy1xwXsjM8/
+USATUlvzSnxPiBbo4HxNMyVmVO/xHyR+Yi2h8fwdMF10hlGu0jdsTTrlTcuy4eFN
+HB6n+werd+a9k1IUvNAvIjopcNQsn9svsqOwXxnt8kxfEQHEdmL51vTKpA1QqFZG
+PpVMj2bkkDzXRHz4JV4VuChPnbjfkPCDuHiSpzSUGgcRCq3/jPidgNp6Os+MEzAM
+5HgBja9P33epLfAr46UBus26DWa2iBUm0V48a1aQ4ugRaiQHxXjnjSSYlsvggbQg
+raSIKKzt/PmigE3Iyj0EpkAgBHVQG1KttLFLqpMuMARfJQ==
+-----END SSH SIGNATURE-----
diff --git a/internal/gitaly/service/operations/cherry_pick_test.go b/internal/gitaly/service/operations/cherry_pick_test.go
index 66ed82a88..1b5ef721e 100644
--- a/internal/gitaly/service/operations/cherry_pick_test.go
+++ b/internal/gitaly/service/operations/cherry_pick_test.go
@@ -524,7 +524,7 @@ func testServerUserCherryPickMergeCommit(t *testing.T, ctx context.Context) {
gpgsig, dataWithoutGpgSig := signature.ExtractSignature(t, ctx, data)
- signingKey, err := signature.ParseSigningKey("testdata/signing_ssh_key_ecdsa")
+ signingKey, err := signature.ParseSigningKeys("testdata/signing_ssh_key_ecdsa")
require.NoError(t, err)
require.NoError(t, signingKey.Verify([]byte(gpgsig), []byte(dataWithoutGpgSig)))
diff --git a/internal/gitaly/service/operations/revert_test.go b/internal/gitaly/service/operations/revert_test.go
index a74b954f5..bef89503c 100644
--- a/internal/gitaly/service/operations/revert_test.go
+++ b/internal/gitaly/service/operations/revert_test.go
@@ -587,7 +587,7 @@ func testServerUserRevertMergeCommit(t *testing.T, ctx context.Context) {
gpgsig, dataWithoutGpgSig := signature.ExtractSignature(t, ctx, data)
- signingKey, err := signature.ParseSigningKey("testdata/signing_ssh_key_rsa")
+ signingKey, err := signature.ParseSigningKeys("testdata/signing_ssh_key_rsa")
require.NoError(t, err)
require.NoError(t, signingKey.Verify([]byte(gpgsig), []byte(dataWithoutGpgSig)))
diff --git a/internal/gitaly/service/operations/squash_test.go b/internal/gitaly/service/operations/squash_test.go
index 6fb657066..96969ce77 100644
--- a/internal/gitaly/service/operations/squash_test.go
+++ b/internal/gitaly/service/operations/squash_test.go
@@ -116,7 +116,7 @@ func testUserSquashSuccessful(t *testing.T, ctx context.Context) {
gpgsig, dataWithoutGpgSig := signature.ExtractSignature(t, ctx, data)
- signingKey, err := signature.ParseSigningKey("testdata/signing_ssh_key_ed25519")
+ signingKey, err := signature.ParseSigningKeys("testdata/signing_ssh_key_ed25519")
require.NoError(t, err)
require.NoError(t, signingKey.Verify([]byte(gpgsig), []byte(dataWithoutGpgSig)))
diff --git a/internal/signature/signature.go b/internal/signature/signature.go
index 087af1b0b..6f9418aa0 100644
--- a/internal/signature/signature.go
+++ b/internal/signature/signature.go
@@ -6,14 +6,48 @@ import (
"os"
)
-// SigningKey is the common interface interface of SSH and GPG signing keys
+// SigningKey is the common interface of SSH and GPG signing keys
type SigningKey interface {
CreateSignature([]byte) ([]byte, error)
Verify([]byte, []byte) error
}
-// ParseSigningKey parses a signing key and returns either GPG or SSH key
-func ParseSigningKey(path string) (SigningKey, error) {
+// SigningKeys represents all signing keys configured in the system.
+// The primary key is used for creating signatures, the secondary
+// keys are used for verification if the primary key failed to verify
+// a signature
+type SigningKeys struct {
+ primaryKey SigningKey
+ secondaryKeys []SigningKey
+}
+
+// ParseSigningKeys parses a list of signing keys separated by a comma and returns
+// a list of GPG or SSH keys.
+// Multiple signing keys are necessary to provide proper key rotation.
+// The latest signing key is specified first and used for creating a signature. The
+// previous signing keys go after and are used to verify a signature.
+func ParseSigningKeys(primaryPath string, secondaryPaths ...string) (*SigningKeys, error) {
+ primaryKey, err := parseSigningKey(primaryPath)
+ if err != nil {
+ return nil, err
+ }
+
+ secondaryKeys := make([]SigningKey, 0, len(secondaryPaths))
+ for _, path := range secondaryPaths {
+ signingKey, err := parseSigningKey(path)
+ if err != nil {
+ return nil, err
+ }
+ secondaryKeys = append(secondaryKeys, signingKey)
+ }
+
+ return &SigningKeys{
+ primaryKey: primaryKey,
+ secondaryKeys: secondaryKeys,
+ }, nil
+}
+
+func parseSigningKey(path string) (SigningKey, error) {
key, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("open file: %w", err)
@@ -25,3 +59,22 @@ func ParseSigningKey(path string) (SigningKey, error) {
return parseGpgSigningKey(key)
}
+
+// CreateSignature uses the primary key to create a signature
+func (s *SigningKeys) CreateSignature(contentToSign []byte) ([]byte, error) {
+ return s.primaryKey.CreateSignature(contentToSign)
+}
+
+// Verify iterates over all signing keys and returns nil if any
+// verification was successful. Otherwise, the last error is returned.
+// Note: when Golang 1.19 is no longer supported, can be refactored using errors.Join
+func (s *SigningKeys) Verify(signature, signedText []byte) error {
+ var err error
+ for _, signingKey := range append([]SigningKey{s.primaryKey}, s.secondaryKeys...) {
+ err = signingKey.Verify(signature, signedText)
+ if err == nil {
+ return nil
+ }
+ }
+ return err
+}
diff --git a/internal/signature/signature_test.go b/internal/signature/signature_test.go
new file mode 100644
index 000000000..8b25af277
--- /dev/null
+++ b/internal/signature/signature_test.go
@@ -0,0 +1,41 @@
+package signature
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+var commit = []byte(`
+tree 86ec18bfe87ad42a782fdabd8310f9b7ac750f51
+parent b83d6e391c22777fca1ed3012fce84f633d7fed0
+parent 4a24d82dbca5c11c61556f3b35ca472b7463187e
+author User <user@email> 1491906794 +0000
+committer User <user@email> 1491906794 +0000
+
+Update README.md to include
+`)
+
+func TestParseSigningKeys(t *testing.T) {
+ primaryPath := "testdata/signing_key.ssh"
+ secondaryPaths := []string{"testdata/signing_key.gpg"}
+
+ expectedSSHSignature, err := os.ReadFile("testdata/signing_key.ssh.sig")
+ require.NoError(t, err)
+
+ expectedGPGSignature, err := os.ReadFile("testdata/signing_key.gpg.sig")
+ require.NoError(t, err)
+
+ signingKeys, err := ParseSigningKeys(primaryPath, secondaryPaths...)
+ require.NoError(t, err)
+ require.NotNil(t, signingKeys.primaryKey)
+ require.Len(t, signingKeys.secondaryKeys, 1)
+
+ signature, err := signingKeys.CreateSignature(commit)
+ require.NoError(t, err)
+ require.Equal(t, signature, expectedSSHSignature)
+
+ require.NoError(t, signingKeys.Verify(expectedSSHSignature, commit))
+ require.NoError(t, signingKeys.Verify(expectedGPGSignature, commit))
+}
diff --git a/internal/signature/testdata/signing_key.gpg b/internal/signature/testdata/signing_key.gpg
new file mode 100644
index 000000000..11c9da480
--- /dev/null
+++ b/internal/signature/testdata/signing_key.gpg
Binary files differ
diff --git a/internal/signature/testdata/signing_key.gpg.sig b/internal/signature/testdata/signing_key.gpg.sig
new file mode 100644
index 000000000..a40517bfe
--- /dev/null
+++ b/internal/signature/testdata/signing_key.gpg.sig
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+
+wnUEARYIACcFAmTNFy4JEPJXiadkCMyQFiEExjVDV9HQA2mdyUaP8leJp2QIzJAA
+AKDJAP9vieT9F6rAp8DSgEgBv+MWNbTAGUAcUjljlIB4P3pIJQD/f4Vu1rPZaeZK
+mdpHBF/BTvsnIaAMifLzwQoIj0JSBg4=
+=rbyz
+-----END PGP SIGNATURE----- \ No newline at end of file
diff --git a/internal/signature/testdata/signing_key.ssh b/internal/signature/testdata/signing_key.ssh
new file mode 100644
index 000000000..81476de35
--- /dev/null
+++ b/internal/signature/testdata/signing_key.ssh
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBXMpA2lE+8eKF8lAn4KiYXwF0W4bbC5eH4uNeNs6zwiAAAAKiS/Kcbkvyn
+GwAAAAtzc2gtZWQyNTUxOQAAACBXMpA2lE+8eKF8lAn4KiYXwF0W4bbC5eH4uNeNs6zwiA
+AAAEDhp93OSvbbx0XSDEbfGhI4xna38KQ5DxXDsTK4vjUUAlcykDaUT7x4oXyUCfgqJhfA
+XRbhtsLl4fi4142zrPCIAAAAJWlnb3Jkcm96ZG92QElnb3JzLU1hY0Jvb2stUHJvLTIubG
+9jYWw=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/signature/testdata/signing_key.ssh.sig b/internal/signature/testdata/signing_key.ssh.sig
new file mode 100644
index 000000000..1edc915fa
--- /dev/null
+++ b/internal/signature/testdata/signing_key.ssh.sig
@@ -0,0 +1,6 @@
+-----BEGIN SSH SIGNATURE-----
+U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgVzKQNpRPvHihfJQJ+Com
+F8BdFuG2wuXh+LjXjbOs8IgAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3Nz
+aC1lZDI1NTE5AAAAQBLSO2693UpEHPBoRyIn5qd5Q/BZ01ndPJbktgS65qzcCdr0
+Z0pe50ZBFfQ5rsWkN7dQGFZ6A/LLfEt4IIYY4Aw=
+-----END SSH SIGNATURE-----