diff options
author | Hidde Beydals <hidde@hhh.computer> | 2023-02-27 21:42:45 +0100 |
---|---|---|
committer | Hidde Beydals <hidde@hhh.computer> | 2023-02-27 22:08:42 +0100 |
commit | f1dc529fac28e6c45882292184270f94b5d30b7f (patch) | |
tree | c0ea214143dc9746dda0615422d7c7437aef395c /plumbing/object/signature.go | |
parent | 7ab4957732a817bada223e5c361f0c9753d9e40c (diff) | |
download | go-git-f1dc529fac28e6c45882292184270f94b5d30b7f.tar.gz |
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 <hidde@hhh.computer>
Diffstat (limited to 'plumbing/object/signature.go')
-rw-r--r-- | plumbing/object/signature.go | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/plumbing/object/signature.go b/plumbing/object/signature.go new file mode 100644 index 0000000..91cf371 --- /dev/null +++ b/plumbing/object/signature.go @@ -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 +} |