+// verify an epoint document, return the cleaned version as well
+func Verify(c *Signed, key openpgp.KeyRing) (err error) {
+ msg := bytes.NewBuffer(c.Body)
+ sig := bytes.NewBuffer(c.Signature)
+// TODO: verify signature
+ _,_ = msg,sig
+// _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
+ return
+}
+
+// sign body with given secret key
+func Sign(body []byte, key *openpgp.Entity) (c *Signed, err error) {
+ c = new(Signed)
+ c.Hash = "SHA256"
+ c.Body = body
+ w := new(bytes.Buffer)
+ err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
+ c.Signature = w.Bytes()
+ return
+}
+
+// split a clear signed document into body and armored signature
+func ParseSigned(s []byte) (c *Signed, err error) {
+ // look for clear signed header
+ for !bytes.HasPrefix(s, []byte(ClearSignedHeader)) {
+ _, s = getLine(s)
+ if len(s) == 0 {
+ err = fmt.Errorf("ParseSigned: clear signed header is missing")
+ return
+ }
+ }
+ s = s[len(ClearSignedHeader):]
+ // end of line after the header
+ empty, s := getLine(s)
+ if len(empty) != 0 {
+ err = fmt.Errorf("ParseSigned: bad clear signed header")
+ return
+ }
+ // skip all hash headers, section 7.
+ for bytes.HasPrefix(s, []byte("Hash: ")) {
+ _, s = getLine(s)
+ }
+ // skip empty line
+ empty, s = getLine(s)
+ if len(empty) != 0 {
+ err = fmt.Errorf("ParseSigned: expected an empty line after armor headers")
+ return
+ }
+ lines := [][]byte{}
+ for !bytes.HasPrefix(s, []byte("-----BEGIN")) {
+ var line []byte
+ line, s = getLine(s)
+ // dash unescape, section 7.1.
+ if bytes.HasPrefix(line, []byte("- ")) {
+ line = line[2:]
+ }
+ // empty values are not supported: "Key: \n"
+ lines = append(lines, bytes.TrimRight(line, " \t"))
+ }
+ c = new(Signed)
+ // last line is not closed by \n
+ c.Body = bytes.Join(lines, []byte("\n"))
+ // signature is just the rest of the input data
+ c.Signature = s
+ return
+}
+
+// clean up, check and reencode signature
+// used on drafts before calculating the signed document hash
+func CleanSigned(c *Signed) (err error) {
+ b, err := armor.Decode(bytes.NewBuffer(c.Signature))
+ if err != nil {
+ return
+ }
+ if b.Type != openpgp.SignatureType {
+ err = fmt.Errorf("CleanSigned: invalid armored signature type")
+ return
+ }
+ p, err := packet.Read(b.Body)
+ if err != nil {
+ return
+ }
+ sig, ok := p.(*packet.Signature)
+ if !ok {
+ err = fmt.Errorf("CleanSigned: invalid signature packet")
+ return
+ }
+ // section 5.2.3
+ if sig.SigType != packet.SigTypeText {
+ err = fmt.Errorf("CleanSigned: expected text signature")
+ return
+ }
+ switch sig.Hash {
+ case crypto.SHA1:
+ c.Hash = "SHA1"
+ case crypto.SHA256:
+ c.Hash = "SHA256"
+ default:
+ err = fmt.Errorf("CleanSigned: expected SHA1 or SHA256 signature hash")
+ return
+ }
+ // TODO: check CreationTime and other subpackets
+ if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
+ err = fmt.Errorf("CleanSigned: signature must not expire")
+ return
+ }
+ out := new(bytes.Buffer)
+ w, err := armor.Encode(out, openpgp.SignatureType, nil)
+ if err != nil {
+ return
+ }
+ err = sig.Serialize(w)
+ if err != nil {
+ return
+ }
+ err = w.Close()
+ if err != nil {
+ return
+ }
+ c.Signature = out.Bytes()
+ return
+}
+
+// create clear signed document
+func FormatSigned(c *Signed) (data []byte, err error) {
+ s := ClearSignedHeader + "\n"