Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #690 from hiddeco/recognize-tag-signatures
plumbing: support SSH/X509 signed tags
- Loading branch information
Showing
4 changed files
with
307 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package object | ||
|
||
import "bytes" | ||
|
||
const ( | ||
signatureTypeUnknown signatureType = iota | ||
signatureTypeOpenPGP | ||
signatureTypeX509 | ||
signatureTypeSSH | ||
) | ||
|
||
var ( | ||
// openPGPSignatureFormat is the format of an OpenPGP signature. | ||
openPGPSignatureFormat = signatureFormat{ | ||
[]byte("-----BEGIN PGP SIGNATURE-----"), | ||
[]byte("-----BEGIN PGP MESSAGE-----"), | ||
} | ||
// x509SignatureFormat is the format of an X509 signature, which is | ||
// a PKCS#7 (S/MIME) signature. | ||
x509SignatureFormat = signatureFormat{ | ||
[]byte("-----BEGIN CERTIFICATE-----"), | ||
} | ||
|
||
// sshSignatureFormat is the format of an SSH signature. | ||
sshSignatureFormat = signatureFormat{ | ||
[]byte("-----BEGIN SSH SIGNATURE-----"), | ||
} | ||
) | ||
|
||
var ( | ||
// knownSignatureFormats is a map of known signature formats, indexed by | ||
// their signatureType. | ||
knownSignatureFormats = map[signatureType]signatureFormat{ | ||
signatureTypeOpenPGP: openPGPSignatureFormat, | ||
signatureTypeX509: x509SignatureFormat, | ||
signatureTypeSSH: sshSignatureFormat, | ||
} | ||
) | ||
|
||
// signatureType represents the type of the signature. | ||
type signatureType int8 | ||
|
||
// signatureFormat represents the beginning of a signature. | ||
type signatureFormat [][]byte | ||
|
||
// typeForSignature returns the type of the signature based on its format. | ||
func typeForSignature(b []byte) signatureType { | ||
for t, i := range knownSignatureFormats { | ||
for _, begin := range i { | ||
if bytes.HasPrefix(b, begin) { | ||
return t | ||
} | ||
} | ||
} | ||
return signatureTypeUnknown | ||
} | ||
|
||
// parseSignedBytes returns the position of the last signature block found in | ||
// the given bytes. If no signature block is found, it returns -1. | ||
// | ||
// When multiple signature blocks are found, the position of the last one is | ||
// returned. Any tailing bytes after this signature block start should be | ||
// considered part of the signature. | ||
// | ||
// Given this, it would be safe to use the returned position to split the bytes | ||
// into two parts: the first part containing the message, the second part | ||
// containing the signature. | ||
// | ||
// Example: | ||
// | ||
// message := []byte(`Message with signature | ||
// | ||
// -----BEGIN SSH SIGNATURE----- | ||
// ...`) | ||
// | ||
// var signature string | ||
// if pos, _ := parseSignedBytes(message); pos != -1 { | ||
// signature = string(message[pos:]) | ||
// message = message[:pos] | ||
// } | ||
// | ||
// This logic is on par with git's gpg-interface.c:parse_signed_buffer(). | ||
// https://github.com/git/git/blob/7c2ef319c52c4997256f5807564523dfd4acdfc7/gpg-interface.c#L668 | ||
func parseSignedBytes(b []byte) (int, signatureType) { | ||
var n, match = 0, -1 | ||
var t signatureType | ||
for n < len(b) { | ||
var i = b[n:] | ||
if st := typeForSignature(i); st != signatureTypeUnknown { | ||
match = n | ||
t = st | ||
} | ||
if eol := bytes.IndexByte(i, '\n'); eol >= 0 { | ||
n += eol + 1 | ||
continue | ||
} | ||
// If we reach this point, we've reached the end. | ||
break | ||
} | ||
return match, t | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters