package config import ( "errors" log "github.com/sirupsen/logrus" "github.com/go-i2p/go-i2p/lib/common" "strings" "unicode/utf8" ) // // https://geti2p.net/spec/updates // const SU3_MAGIC_BYTES = "I2Psu3" const SU3_MAGIC_BYTE_LEN = 6 const SU3_SIGNATURE_TYPE_LEN = 2 const SU3_SIGNATURE_LENGTH_LEN = 2 const SU3_CONTENT_LENGTH_LEN = 8 var SU3_SIGNATURE_TYPE_DSA_SHA1 = "DSA-SHA1" var SU3_SIGNATURE_TYPE_ECDSA_SHA256_P256 = "ECDSA-SHA256-P256" var SU3_SIGNATURE_TYPE_ECDSA_SHA384_P384 = "ECDSA-SHA384-P384" var SU3_SIGNATURE_TYPE_ECDSA_SHA512_P521 = "ECDSA-SHA512-P521" var SU3_SIGNATURE_TYPE_RSA_SHA256_2048 = "RSA-SHA256-2048" var SU3_SIGNATURE_TYPE_RSA_SHA384_3072 = "RSA-SHA384-3072" var SU3_SIGNATURE_TYPE_RSA_SHA512_4096 = "RSA-SHA512-4096" var SU3_SIGNATURE_TYPE_EdDSA_SHA512_Ed25519ph = "EdDSA-SHA512-Ed25519ph" var SU3_FILE_TYPE_ZIP = "zip" var SU3_FILE_TYPE_XML = "xml" var SU3_FILE_TYPE_HTML = "html" var SU3_FILE_TYPE_XML_GZ = "xml.gz" var SU3_FILE_TYPE_TXT_GZ = "txt.gz" var SU3_CONTENT_TYPE_UNKNOWN = "unknown" var SU3_CONTENT_TYPE_ROUTER_UPDATE = "router_update" var SU3_CONTENT_TYPE_PLUGIN_UPDATE = "plugin_update" var SU3_CONTENT_TYPE_RESEED_DATA = "reseed_data" var SU3_CONTENT_TYPE_NEWS_FEED = "news_feed" var SU3_CONTENT_TYPE_BLOCKLIST_FEED = "blocklist_feed" var SU3_SIGNATURE_TYPE_MAP = map[[SU3_SIGNATURE_TYPE_LEN]byte]string{ [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x00}: SU3_SIGNATURE_TYPE_DSA_SHA1, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x01}: SU3_SIGNATURE_TYPE_ECDSA_SHA256_P256, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x02}: SU3_SIGNATURE_TYPE_ECDSA_SHA384_P384, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x03}: SU3_SIGNATURE_TYPE_ECDSA_SHA512_P521, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x04}: SU3_SIGNATURE_TYPE_RSA_SHA256_2048, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x05}: SU3_SIGNATURE_TYPE_RSA_SHA384_3072, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x06}: SU3_SIGNATURE_TYPE_RSA_SHA512_4096, [SU3_SIGNATURE_TYPE_LEN]byte{0x00, 0x08}: SU3_SIGNATURE_TYPE_EdDSA_SHA512_Ed25519ph, } var SU3_FILE_TYPE_MAP = map[byte]string{ 0x00: SU3_FILE_TYPE_ZIP, 0x01: SU3_FILE_TYPE_XML, 0x02: SU3_FILE_TYPE_HTML, 0x03: SU3_FILE_TYPE_XML_GZ, 0x04: SU3_FILE_TYPE_TXT_GZ, } var SU3_CONTENT_TYPE_MAP = map[byte]string{ 0x00: SU3_CONTENT_TYPE_UNKNOWN, 0x01: SU3_CONTENT_TYPE_ROUTER_UPDATE, 0x02: SU3_CONTENT_TYPE_PLUGIN_UPDATE, 0x03: SU3_CONTENT_TYPE_RESEED_DATA, 0x04: SU3_CONTENT_TYPE_NEWS_FEED, 0x05: SU3_CONTENT_TYPE_BLOCKLIST_FEED, } var ERR_NOT_ENOUGH_SU3_DATA = errors.New("not enough data for su3") var ERR_SU3_MAGIC_BYTES_MISMATCH = errors.New("magic bytes do not match I2Psu3") var ERR_SU3_UNUSED_BYTE_WITH_DATA = errors.New("unused byte in su3 specification contains data") var ERR_SU3_SIGNATURE_TYPE_UNKNOWN = errors.New("unknown signature type") var ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN = errors.New("unknown file format version") var ERR_SU3_VERSION_LENGTH_TOO_SMALL = errors.New("version length is too small") var ERR_SU3_FILE_TYPE_UNKNOWN = errors.New("unknown file type") var ERR_SU3_CONTENT_TYPE_UNKNOWN = errors.New("unknown content type") var ERR_SU3_VERSION_NOT_UTF8 = errors.New("version not utf8") var ERR_SU3_SIGNER_ID_NOT_UTF8 = errors.New("version not utf8") type SU3 struct { Raw []byte FileFormatVersion int SignatureType string SignatureLength int VersionLength int SignerIDLength int ContentLength int FileType string ContentType string Version string SignerID string Content []byte Signature []byte } func OpenSU3() {} func ReadSU3(data []byte) (SU3, error) { su3 := SU3{ Raw: data, } if err := checkMagicBytes(data); err != nil { return su3, err } if err := checkByte6Unused(data); err != nil { return su3, err } file_format_version, err := getFileFormatVersion(data) su3.FileFormatVersion = file_format_version if err != nil { return su3, err } signature_type, err := getSignatureType(data) su3.SignatureType = signature_type if err != nil { return su3, err } signature_length, err := getSignatureLength(data) su3.SignatureLength = signature_length if err != nil { return su3, err } if err := checkByte12Unused(data); err != nil { return su3, err } version_length, err := getVersionLength(data) su3.VersionLength = version_length if err != nil { return su3, err } if err := checkByte14Unused(data); err != nil { return su3, err } signer_id_length, err := getSignerIDLength(data) su3.SignerIDLength = signer_id_length if err != nil { return su3, err } content_length, err := getContentLength(data) su3.ContentLength = content_length if err != nil { return su3, err } file_type, err := getFileType(data) su3.FileType = file_type if err != nil { return su3, err } if err := checkByte26Unused(data); err != nil { return su3, err } content_type, err := getContentType(data) su3.ContentType = content_type if err != nil { return su3, err } if err := checkBytes28To39Unused(data); err != nil { return su3, err } version, err := getVersion(data) su3.Version = version if err != nil { return su3, err } signer_id, err := getSignerID(data) su3.SignerID = signer_id if err != nil { return su3, err } content, err := getContent(data) su3.Content = content if err != nil { return su3, err } signature, err := getSignature(data) su3.Signature = signature if err != nil { return su3, err } return su3, nil } func checkMagicBytes(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN { return ERR_NOT_ENOUGH_SU3_DATA } magic_str := string(data[:SU3_MAGIC_BYTE_LEN]) if magic_str != SU3_MAGIC_BYTES { log.WithFields(log.Fields{ "at": "config.checkMagicBytes", "expected": []byte(SU3_MAGIC_BYTES), "got": []byte(magic_str), }).Debug(ERR_SU3_MAGIC_BYTES_MISMATCH) return ERR_SU3_MAGIC_BYTES_MISMATCH } return nil } func checkByte6Unused(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN+1 { return ERR_NOT_ENOUGH_SU3_DATA } unused_byte := data[SU3_MAGIC_BYTE_LEN] if unused_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.checkByte6Unused", "byte_data": unused_byte, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } return nil } func getFileFormatVersion(data []byte) (int, error) { if len(data) < SU3_MAGIC_BYTE_LEN+1+1 { return 0, ERR_NOT_ENOUGH_SU3_DATA } if file_format_version_byte := data[SU3_MAGIC_BYTE_LEN+1]; file_format_version_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.getSignatureType", "file_format_version_byte": file_format_version_byte, }).Debug(ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN) return int(file_format_version_byte), ERR_SU3_FILE_FORMAT_VERSION_UNKNOWN } return 0, nil } func getSignatureType(data []byte) (string, error) { signature_type := "" if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN { return signature_type, ERR_NOT_ENOUGH_SU3_DATA } signature_type_bytes := [2]byte{} copy( signature_type_bytes[:], data[SU3_MAGIC_BYTE_LEN+1+1:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN], ) if str, ok := SU3_SIGNATURE_TYPE_MAP[signature_type_bytes]; !ok { log.WithFields(log.Fields{ "at": "config.getSignatureType", "type": signature_type_bytes, }).Debug(ERR_SU3_SIGNATURE_TYPE_UNKNOWN) return signature_type, ERR_SU3_SIGNATURE_TYPE_UNKNOWN } else { signature_type = str } return signature_type, nil } func getSignatureLength(data []byte) (int, error) { signature_length := 0 if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN { return signature_length, ERR_NOT_ENOUGH_SU3_DATA } signature_length_bytes := [2]byte{} copy( signature_length_bytes[:], data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN], ) signature_length = common.Integer(signature_length_bytes[:]) return signature_length, nil } func checkByte12Unused(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1 { return ERR_NOT_ENOUGH_SU3_DATA } unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN] if unused_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.checkByte12Unused", "byte_data": unused_byte, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } return nil } func getVersionLength(data []byte) (int, error) { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1 { return 0, ERR_NOT_ENOUGH_SU3_DATA } version_length := common.Integer([]byte{data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1]}) if version_length < 16 { log.WithFields(log.Fields{ "at": "config.getSignatureType", "version_length": version_length, }).Debug(ERR_SU3_VERSION_LENGTH_TOO_SMALL) return version_length, ERR_SU3_VERSION_LENGTH_TOO_SMALL } return version_length, nil } func checkByte14Unused(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1 { return ERR_NOT_ENOUGH_SU3_DATA } unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1] if unused_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.checkByte14Unused", "byte_data": unused_byte, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } return nil } func getSignerIDLength(data []byte) (int, error) { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1 { return 0, ERR_NOT_ENOUGH_SU3_DATA } signer_id_length := common.Integer([]byte{data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1]}) return signer_id_length, nil } func getContentLength(data []byte) (int, error) { content_length := 0 if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN { return content_length, ERR_NOT_ENOUGH_SU3_DATA } content_length_bytes := [8]byte{} copy( content_length_bytes[:], data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1:SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN], ) content_length = common.Integer(content_length_bytes[:]) return content_length, nil } func checkByte24Unused(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1 { return ERR_NOT_ENOUGH_SU3_DATA } unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN] if unused_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.checkByte24Unused", "byte_data": unused_byte, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } return nil } func getFileType(data []byte) (string, error) { file_type := "" if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1 { return file_type, ERR_NOT_ENOUGH_SU3_DATA } file_type_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1] if str, ok := SU3_FILE_TYPE_MAP[file_type_byte]; !ok { log.WithFields(log.Fields{ "at": "config.getFileType", "type": file_type_byte, }).Debug(ERR_SU3_FILE_TYPE_UNKNOWN) return file_type, ERR_SU3_FILE_TYPE_UNKNOWN } else { file_type = str } return file_type, nil } func checkByte26Unused(data []byte) error { if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1 { return ERR_NOT_ENOUGH_SU3_DATA } unused_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1] if unused_byte != 0x00 { log.WithFields(log.Fields{ "at": "config.checkByt26Unused", "byte_data": unused_byte, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } return nil } func getContentType(data []byte) (string, error) { content_type := "" if len(data) < SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1+1 { return content_type, ERR_NOT_ENOUGH_SU3_DATA } content_type_byte := data[SU3_MAGIC_BYTE_LEN+1+1+SU3_SIGNATURE_TYPE_LEN+SU3_SIGNATURE_LENGTH_LEN+1+1+1+1+SU3_CONTENT_LENGTH_LEN+1+1+1] if str, ok := SU3_CONTENT_TYPE_MAP[content_type_byte]; !ok { log.WithFields(log.Fields{ "at": "config.getContentType", "type": content_type_byte, }).Debug(ERR_SU3_CONTENT_TYPE_UNKNOWN) return content_type, ERR_SU3_CONTENT_TYPE_UNKNOWN } else { content_type = str } return content_type, nil } func checkBytes28To39Unused(data []byte) error { end := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 if len(data) < end { return ERR_NOT_ENOUGH_SU3_DATA } unused_bytes := [12]byte{} copy( unused_bytes[:], data[end-12:end], ) for i, value := range unused_bytes { if value != 0x00 { log.WithFields(log.Fields{ "at": "config.checkBytes28To39Unused", "iterator": i, }).Debug(ERR_SU3_UNUSED_BYTE_WITH_DATA) return ERR_SU3_UNUSED_BYTE_WITH_DATA } } return nil } func getVersion(data []byte) (string, error) { version := "" version_length, err := getVersionLength(data) if err != nil { return version, err } min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 if len(data) < min+version_length { return version, ERR_NOT_ENOUGH_SU3_DATA } version_bytes := data[min : min+version_length] version_str := strings.TrimRight(string(version_bytes), "\x00") if !utf8.ValidString(version_str) { log.WithFields(log.Fields{ "at": "config.getVersion", }).Debug(ERR_SU3_VERSION_NOT_UTF8) return version, ERR_SU3_VERSION_NOT_UTF8 } else { version = version_str } return version, nil } func getSignerID(data []byte) (string, error) { signer_id := "" signer_id_length, err := getSignerIDLength(data) if err != nil { return signer_id, err } version_length, err := getVersionLength(data) if err != nil { return signer_id, err } min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length if len(data) < min+signer_id_length { return signer_id, ERR_NOT_ENOUGH_SU3_DATA } signer_id_bytes := data[min : min+signer_id_length] signer_id_str := string(signer_id_bytes) if !utf8.ValidString(signer_id_str) { log.WithFields(log.Fields{ "at": "config.getSignerID", }).Debug(ERR_SU3_SIGNER_ID_NOT_UTF8) return signer_id, ERR_SU3_SIGNER_ID_NOT_UTF8 } else { signer_id = signer_id_str } return signer_id, nil } func getContent(data []byte) ([]byte, error) { content := []byte{} content_length, err := getContentLength(data) if err != nil { return content, err } signer_id_length, err := getSignerIDLength(data) if err != nil { return content, err } version_length, err := getVersionLength(data) if err != nil { return content, err } min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length + signer_id_length if len(data) < min+content_length { return content, ERR_NOT_ENOUGH_SU3_DATA } content = data[min : min+content_length] return content, nil } func getSignature(data []byte) ([]byte, error) { signature := []byte{} signature_length, err := getSignatureLength(data) if err != nil { return signature, err } content_length, err := getContentLength(data) if err != nil { return signature, err } signer_id_length, err := getSignerIDLength(data) if err != nil { return signature, err } version_length, err := getVersionLength(data) if err != nil { return signature, err } min := SU3_MAGIC_BYTE_LEN + 1 + 1 + SU3_SIGNATURE_TYPE_LEN + SU3_SIGNATURE_LENGTH_LEN + 1 + 1 + 1 + 1 + SU3_CONTENT_LENGTH_LEN + 1 + 1 + 1 + 1 + 12 + version_length + signer_id_length + content_length if len(data) < min+signature_length { return signature, ERR_NOT_ENOUGH_SU3_DATA } signature = data[min : min+signature_length] return signature, nil }