Skip to content

Instantly share code, notes, and snippets.

@vagmi
Created August 13, 2018 17:21
Show Gist options
  • Save vagmi/5aaae73c8f0981e991c970293e7c62ca to your computer and use it in GitHub Desktop.
Save vagmi/5aaae73c8f0981e991c970293e7c62ca to your computer and use it in GitHub Desktop.
AS2 (RFC 4130) in Golang
package main
import (
"bytes"
"crypto"
_ "crypto/md5" // for crypto.MD5
_ "crypto/sha1" // for crypto.SHA1
_ "crypto/sha512" // for crypto.SHA384 & 512
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"github.com/dawud-tan/pkcs7"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/http"
"os"
"regexp"
"strings"
)
func main() {
recpcert, recpkey := LoadX509KeyPair("/home/dawud_tan/app/kepabeanan.crt", "/home/dawud_tan/app/kepabeanan.key")
http.HandleFunc("/as2-golang", func(w http.ResponseWriter, r *http.Request) {
encrypted, err := entityBodyToPkcs7(r.Body)
checkError(err)
defer r.Body.Close()
decrypted, err := encrypted.Decrypt(recpcert, recpkey)
if err != nil {
fmt.Fprint(w, "Tidak bisa mendekripsi") //perlu dilaporkan dengan MDN
} else {
signed, isinya := parseMultipartSigned(decrypted)
calculatedMIC := calculateMIC(signed.Signers[0].DigestAlgorithm.Algorithm, isinya)
fmt.Println("calculatedMIC: " + calculatedMIC)
signed.Content = []byte(isinya)
if err := signed.Verify(); err != nil {
fmt.Println(err) //perlu dilaporkan dengan MDN
fmt.Fprint(w, "Tanda Tangan Tidak Valid") //perlu dilaporkan dengan MDN
} else {
aReportParts := &bytes.Buffer{}
aReportWriter := multipart.NewWriter(aReportParts)
fmt.Fprint(aReportParts, "Content-Type: multipart/report; report-type=disposition-notification; \r\n")
fmt.Fprintf(aReportParts, "\tboundary=\"%s\"\r\n\r\n", aReportWriter.Boundary())
fmt.Fprintf(aReportParts, "--%s\r\n", aReportWriter.Boundary())
fmt.Fprint(aReportParts, "Content-Type: text/plain\r\n")
fmt.Fprint(aReportParts, "Content-Transfer-Encoding: 7bit\r\n\r\n")
fmt.Fprint(aReportParts, "The AS2 message has been received.\r\n")
fmt.Fprintf(aReportParts, "--%s\r\n", aReportWriter.Boundary())
fmt.Fprint(aReportParts, "Content-Type: message/disposition-notification\r\n")
fmt.Fprint(aReportParts, "Content-Transfer-Encoding: 7bit\r\n\r\n")
fmt.Fprint(aReportParts, "Reporting-UA: php AS2 Server\r\n")
fmt.Fprintf(aReportParts, "Original-Recipient: rfc822; %s\r\n", r.Header.Get("AS2-To"))
fmt.Fprintf(aReportParts, "Final-Recipient: rfc822; %s\r\n", r.Header.Get("AS2-To"))
fmt.Fprintf(aReportParts, "Original-Message-ID: %s\r\n", r.Header.Get("Message-Id"))
fmt.Fprint(aReportParts, "Disposition: automatic-action/MDN-sent-automatically; processed\r\n")
micAlg := strings.Split(r.Header.Get("Disposition-Notification-Options"), "; ")[1]
micAlg = strings.Split(micAlg, ", ")[1]
fmt.Fprintf(aReportParts, "Received-Content-MIC: %s, %s\r\n\r\n", calculatedMIC, micAlg)
aReportWriter.Close()
signedData, _ := pkcs7.NewSignedData([]byte(aReportParts.String()))
signedData.AddSigner(recpcert, recpkey, pkcs7.SignerInfoConfig{})
signedData.Detach()
detachedSignature, _ := signedData.Finish()
balasan := pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: detachedSignature})
aje := string(balasan)
aje = strings.Replace(aje, "-----BEGIN PKCS7-----\n", "", 1)
aje = strings.Replace(aje, "\n-----END PKCS7-----\n", "", 1)
MDN := &bytes.Buffer{}
MDNWriter := multipart.NewWriter(MDN)
w.Header().Set("Content-Type",
"multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=\"sha1\"; boundary=\""+MDNWriter.Boundary()+"\"")
fmt.Fprintf(MDN, "--%s\r\n", MDNWriter.Boundary())
fmt.Fprintf(MDN, "%s\r\n", aReportParts.String())
fmt.Fprintf(MDN, "--%s\r\n", MDNWriter.Boundary())
fmt.Fprint(MDN, "Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\r\n")
fmt.Fprint(MDN, "Content-Transfer-Encoding: base64\r\n")
fmt.Fprint(MDN, "Content-Disposition: attachment; filename=\"smime.p7s\"\r\n\r\n")
fmt.Fprintf(MDN, "%s\r\n", aje)
MDNWriter.Close()
fmt.Fprint(w, MDN.String())
}
}
})
http.ListenAndServe(":8080", nil)
}
func calculateMIC(oid asn1.ObjectIdentifier, isinya string) string {
h, err := getHashForOID(oid)
checkError(err)
hash := h.New()
hash.Write([]byte(isinya))
computed := hash.Sum(nil)
return base64.StdEncoding.EncodeToString([]byte(computed))
}
func parseMultipartSigned(decrypted []byte) (*pkcs7.PKCS7, string) {
r, _ := regexp.Compile("((.|\n)*)boundary(.*)")
multipart_header := r.FindString(string(decrypted))
mediaType, params, err := mime.ParseMediaType(strings.Split(multipart_header, ": ")[1])
checkError(err)
if strings.HasPrefix(mediaType, "multipart/signed") {
mr := multipart.NewReader(bytes.NewReader(decrypted), params["boundary"])
contentPart, err := mr.NextPart()
checkError(err)
content, err := ioutil.ReadAll(contentPart)
checkError(err)
signaturePart, err := mr.NextPart()
checkError(err)
signature, err := entityBodyToPkcs7(signaturePart)
checkError(err)
mimeEntity := concatHeader(contentPart.Header) + "\r\n" + string(content)
return signature, mimeEntity
} else {
return nil, ""
}
}
func entityBodyToPkcs7(body io.ReadCloser) (*pkcs7.PKCS7, error) {
pemBlock, err := ioutil.ReadAll(body)
checkError(err)
beginning := []byte("-----BEGIN PKCS7-----\r\n")
ending := []byte("\r\n-----END PKCS7-----")
pemBody := append(beginning, pemBlock...)
pemBody = append(pemBody, ending...)
decoded, _ := pem.Decode(pemBody)
p7, err := pkcs7.Parse(decoded.Bytes)
return p7, err
}
func LoadX509KeyPair(certFile, keyFile string) (*x509.Certificate, interface{}) {
cf, e := ioutil.ReadFile(certFile)
checkError(e)
cpb, _ := pem.Decode(cf)
crt, e := x509.ParseCertificate(cpb.Bytes)
checkError(e)
kf, e := ioutil.ReadFile(keyFile)
checkError(e)
kpb, _ := pem.Decode(kf)
key, e := x509.ParsePKCS8PrivateKey(kpb.Bytes)
checkError(e)
return crt, key
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
func concatHeader(MIMEHeader map[string][]string) string {
var buffer bytes.Buffer
for k, v := range MIMEHeader {
buffer.WriteString(k)
buffer.WriteString(": ")
buffer.WriteString(v[0])
buffer.WriteString("\r\n")
}
return buffer.String()
}
var (
oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
oidDigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
oidDigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
)
var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported")
func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) {
switch {
case oid.Equal(oidDigestAlgorithmSHA1):
return crypto.SHA1, nil
case oid.Equal(oidDigestAlgorithmSHA384):
return crypto.SHA384, nil
case oid.Equal(oidDigestAlgorithmSHA512):
return crypto.SHA512, nil
}
return crypto.Hash(0), ErrUnsupportedAlgorithm
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment