Created
August 13, 2018 17:21
-
-
Save vagmi/5aaae73c8f0981e991c970293e7c62ca to your computer and use it in GitHub Desktop.
AS2 (RFC 4130) in Golang
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
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