From f1dc529fac28e6c45882292184270f94b5d30b7f Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 27 Feb 2023 21:42:45 +0100 Subject: plumbing: support SSH/X509 signed tags This commit enables support for extracting the SSH and X509 signatures from (annotated) Git tags, as an initial step to support the verification of more signatures than just PGP in go-git. The ported logic from Git further ensures that we look for a signature at the tail of an annotation, instead of the first signature we find in the annotation, as this could theoretically result in a faulty signature getting detected if part of a an annotation itself (e.g. by being placed in the middle as part of an inherited message). For commits, no further change is required as the current extraction of any signature (format) from `gpgsig` in the commit header is sufficient for manual verification. In a future iteration, we could add `signature/ssh` and `signature/x509` packages to further enable people to deal with verifying other signatures than PGP. As well as adding additional methods to `Commit` and `Tag` to provide glue between the packages and the most prominent user-facing APIs. Signed-off-by: Hidde Beydals --- plumbing/object/signature_test.go | 180 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 plumbing/object/signature_test.go (limited to 'plumbing/object/signature_test.go') diff --git a/plumbing/object/signature_test.go b/plumbing/object/signature_test.go new file mode 100644 index 0000000..1bdb1d1 --- /dev/null +++ b/plumbing/object/signature_test.go @@ -0,0 +1,180 @@ +package object + +import ( + "bytes" + "testing" +) + +func Test_typeForSignature(t *testing.T) { + tests := []struct { + name string + b []byte + want signatureType + }{ + { + name: "known signature format (PGP)", + b: []byte(`-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQTMqU0ycQ3f6g3PMoWMmmmF4LuV8QUCYGebVwAKCRCMmmmF4LuV +8VtyAP9LbuXAhtK6FQqOjKybBwlV70rLcXVP24ubDuz88VVwSgD+LuObsasWq6/U +TssDKHUR2taa53bQYjkZQBpvvwOrLgc= +=YQUf +-----END PGP SIGNATURE-----`), + want: signatureTypeOpenPGP, + }, + { + name: "known signature format (SSH)", + b: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + want: signatureTypeSSH, + }, + { + name: "known signature format (X509)", + b: []byte(`-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIJALZ9Z3Z9Z3Z9MA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD +VQQGEwJTRTEOMAwGA1UECAwFVGV4YXMxDjAMBgNVBAcMBVRleGFzMQ4wDAYDVQQK +DAVUZXhhczEOMAwGA1UECwwFVGV4YXMxGDAWBgNVBAMMD1RleGFzIENlcnRpZmlj +YXRlMB4XDTE3MDUyNjE3MjY0MloXDTI3MDUyNDE3MjY0MlowgYgxCzAJBgNVBAYT +AlNFMQ4wDAYDVQQIDAVUZXhhczEOMAwGA1UEBwwFVGV4YXMxDjAMBgNVBAoMBVRl +eGFzMQ4wDAYDVQQLDAVUZXhhczEYMBYGA1UEAwwPVGV4YXMgQ2VydGlmaWNhdGUw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQZ9Z3Z9Z3Z9Z3Z9Z3Z9Z3 +-----END CERTIFICATE-----`), + want: signatureTypeX509, + }, + { + name: "unknown signature format", + b: []byte(`-----BEGIN ARBITRARY SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +-----END UNKNOWN SIGNATURE-----`), + want: signatureTypeUnknown, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := typeForSignature(tt.b); got != tt.want { + t.Errorf("typeForSignature() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_parseSignedBytes(t *testing.T) { + tests := []struct { + name string + b []byte + wantSignature []byte + wantType signatureType + }{ + { + name: "detects signature and type", + b: []byte(`signed tag +-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE-----`), + wantSignature: []byte(`-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE-----`), + wantType: signatureTypeOpenPGP, + }, + { + name: "last signature for multiple signatures", + b: []byte(`signed tag +-----BEGIN PGP SIGNATURE----- + +iQGzBAABCAAdFiEE/h5sbbqJFh9j1AdUSqtFFGopTmwFAmB5XFkACgkQSqtFFGop +TmxvgAv+IPjX5WCLFUIMx8hquMZp1VkhQrseE7rljUYaYpga8gZ9s4kseTGhy7Un +61U3Ro6cTPEiQF/FkAGzSdPuGqv0ARBqHDX2tUI9+Zs/K8aG8tN+JTaof0gBcTyI +BLbZVYDTxbS9whxSDewQd0OvBG1m9ISLUhjXo6mbaVvrKXNXTHg40MPZ8ZxjR/vN +hxXXoUVnFyEDo+v6nK56mYtapThDaQQHHzD6D3VaCq3Msog7qAh9/ZNBmgb88aQ3 +FoK8PHMyr5elsV3mE9bciZBUc+dtzjOvp94uQ5ZKUXaPusXaYXnKpVnzhyer6RBI +gJLWtPwAinqmN41rGJ8jDAGrpPNjaRrMhGtbyVUPUf19OxuUIroe77sIIKTP0X2o +Wgp56dYpTst0JcGv/FYCeau/4pTRDfwHAOcDiBQ/0ag9IrZp9P8P9zlKmzNPEraV +pAe1/EFuhv2UDLucAiWM8iDZIcw8iN0OYMOGUmnk0WuGIo7dzLeqMGY+ND5n5Z8J +sZC//k6m +=VhHy +-----END PGP SIGNATURE----- +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + wantSignature: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE-----`), + wantType: signatureTypeSSH, + }, + { + name: "signature with trailing data", + b: []byte(`An invalid + +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE----- + +signed tag`), + wantSignature: []byte(`-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgij/EfHS8tCjolj5uEANXgKzFfp +0D7wOhjWVbYZH6KugAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 +AAAAQIYHMhSVV9L2xwJuV8eWMLjThya8yXgCHDzw3p01D19KirrabW0veiichPB5m+Ihtr +MKEQruIQWJb+8HVXwssA4= +-----END SSH SIGNATURE----- + +signed tag`), + wantType: signatureTypeSSH, + }, + { + name: "data without signature", + b: []byte(`Some message`), + wantSignature: []byte(``), + wantType: signatureTypeUnknown, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pos, st := parseSignedBytes(tt.b) + var signature []byte + if pos >= 0 { + signature = tt.b[pos:] + } + if !bytes.Equal(signature, tt.wantSignature) { + t.Errorf("parseSignedBytes() got = %s for pos = %v, want %s", signature, pos, tt.wantSignature) + } + if st != tt.wantType { + t.Errorf("parseSignedBytes() got1 = %v, want %v", st, tt.wantType) + } + }) + } +} -- cgit