package main // ver. 2.2a // coded by Kenar import ( . "fmt" "os" "encoding/hex" "encoding/pem" "strconv" "path" ) var usage = `usage1: asn1parse [-D] cert.pem usage2: asn1parse [-D] hexstring` // maybe nice. https://oidref.com var oidmap = map[string]string{ "2.5.4.3": "Common Name", "2.5.4.5": "Serial Number", "2.5.4.6": "Country Name", "2.5.4.7": "Locality Name", "2.5.4.8": "State or Province name", "2.5.4.9": "Street Address", "2.5.4.10": "Organization Name", "2.5.4.11": "Organization Unit Name", "2.5.4.17": "Postal Code", "2.5.29.14": "Subject Key Id", "2.5.29.15": "Key Usage", "2.5.29.16": "Private Key Usage Period", "2.5.29.37": "Extended Key Usage", "2.5.29.35": "Authority Key Id", "2.5.29.19": "Basic Constraints", "2.5.29.17": "Subject Alt Name", "2.5.29.32": "Certificate Policies", "2.5.29.31": "CRL Distribution Points", "2.5.29.20": "CRL Number", "1.3.6.1.5.5.7.1.1": "Authority Info Access", "1.3.6.1.5.5.7.48.1": "Authority Info Access Ocsp", "1.3.6.1.5.5.7.48.2": "Authority Info Access Issuers", "1.2.840.113549.1.1.1": "RSA Encryption", "1.2.840.113549.1.1.5": "Sha1 With Rsa Encryption", "1.2.840.113549.1.1.11": "Sha256 With Rsa Encryption", "1.2.840.113549.1.9.1": "Email Address", "1.3.6.1.4.1.11129.2.4.2": "Signed Certificate Timestamp", } func Error(s ... interface{}){ Fprintln(os.Stderr,s[:]...) os.Exit(1) } // quoted from $GOROOT/src/encoding/asn1/common.go // but also look $GOROOT/src/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go // ASN.1 tags represent the type of the following object. // rfc5280 says: TeletexString, BMPString, and UniversalString are included // for backward compatibility, and SHOULD NOT be used for certificates for new subjects. const ( TagBoolean = 1 TagInteger = 2 TagBitString = 3 // note_2 TagOctetString = 4 TagNull = 5 TagOID = 6 TagEnum = 10 TagUTF8String = 12 TagSequence = 16 TagSet = 17 TagNumericString = 18 TagPrintableString = 19 TagT61String = 20 TagIA5String = 22 // ASCII TagUTCTime = 23 TagGeneralizedTime = 24 TagGeneralString = 27 TagBMPString = 30 // note_1 ) // note_1: Unicode (16bits). 'A' is 0x0041 // look: https://docs.microsoft.com/ja-jp/windows/win32/seccertenroll/about-bmpstring // note_2: // let the contents be B[0] B[1] ... B[n-1], // then B[0] denote the number of effective bits in B[n-1] // therefore B[0] must be 0 <= B[0] < 8 // look: https://docs.microsoft.com/ja-jp/windows/win32/seccertenroll/about-bit-string var tags [32] string func tagsInit(){ for k:=0; k < 32; k++ { tags[k] = strconv.Itoa(k) } tags[TagBoolean] = "Boolean" tags[TagInteger] = "Integer" tags[TagBitString] = "BitString" tags[TagOctetString] = "OctetString" tags[TagNull] = "Null" tags[TagOID] = "OID" tags[TagEnum] = "Enum" tags[TagUTF8String] = "UTF8String" tags[TagSequence] = "Sequence" tags[TagSet] = "Set" tags[TagNumericString] = "NumericString" tags[TagPrintableString] = "PrintableString" tags[TagT61String] = "T61String" tags[TagIA5String] = "IA5String" tags[TagUTCTime] = "UTCTime" tags[TagGeneralizedTime] = "GeneralizedTime" tags[TagGeneralString] = "GeneralString" tags[TagBMPString] = "BMPString" } // convert byte sequence to int // need error check for big bs func bsToLength(bs []byte) int { var n int var m int m = len(bs) if m > 4 { // 4 will be enough Error("Length octets too large:",m) } n = 0 for k := 0; k < m; k++ { n = 256*n + int(bs[k]) } return n } func derToLength(bs []byte) (int, int) { var n int var m,mm int var b0 byte m = len(bs) b0 = bs[0] if b0 & 0x80 == 0 { return int(b0), 1 } mm = int(b0 & 0x7f) if mm > 4 || mm > m - 1 { Error("Length octets too large:",m,mm) } switch(mm){ case 0: // Indefinite form, not coded yet Error("Indefinite form") case 0x7f: Error("Reserved form") } n = 0 bs = bs[1:] for k := 0; k < mm; k++ { n = 256*n + int(bs[k]) } return n, mm + 1 } func uintToBs(bs []byte, x int) int { var k,m,n int var base = 128 for k=0; x > 0; k++ { bs[k] = byte(x % base) x /= base } n = k/2 m = k for k=0; k < n; k++ { bs[k],bs[m -k - 1] = bs[m - k - 1],bs[k] } return m // bs[0:m] is the bs } func uintToDer(bs []byte, x int) int { m := uintToBs(bs, x) for k:= 0; k < m-1; k++ { bs[k] |= 0x80 } return m // bs[0:m] is the der } func bsToUint(bs []byte) int { var n int var k int n = 0 m := len(bs) for k=0; k < m; k++ { n = 128*n + int(bs[k]&0x7f) } return n } func derToUint(bs []byte) (int,int) { var k int m := len(bs) for k=0; k < m; k++ { if bs[k]&0x80 == 0 { break } } k++ return bsToUint(bs[:k]), k } func addOidToDer(bs []byte, oid []int) []byte { var j,k int m := oid[0]*40 + oid[1] mm := len(oid) bs[0] = byte(m) j = 1 for k=2; k < mm; k++ { m = oid[k] j += uintToDer(bs[j:],m) //Println(k,m,j) } return bs[:j] } // look $GOROOT/src/encoding/asn1/asn1.go func derToOid(bs []byte) (oid [] int) { var j,k,v int var size int oid = make([]int, 20) v = int(bs[0]) if v < 80 { oid[0] = v/ 40 oid[1] = int(bs[0]) % 40 } else { oid[0] = 2 oid[1] = v - 80 } m := len(bs) //Println(m) for j,k = 2,1; k < m; j++ { oid[j], size = derToUint(bs[k:]) k += size //Println(k, j, size, oid[j]) } return oid[:j] } func dotOid(input []int) (out string) { out += Sprintf("%d",input[0]) for k:=1; k < len(input); k++ { out += Sprintf(".%d",input[k]) } return } var isHexArg bool var indent int var secn [10]int // section number var secl = -1 // section level func iPrintf(fmt string, args ... interface{}) { Printf("%*s",indent,"") Printf(fmt, args[:]...) } // X.690 // Structure of an encoding // (a) identifier octets // (b) length octets // (c) contents octets // (d) end-of-contents octets (only if bs[1] == 0x80) func asn1Parse(bs []byte) (offset int, err error) { var n,m,h int var s string var primitive bool var contents []byte if Dflag { iPrintf("input %d %x\n", len(bs), bs) } b0 := bs[0] h = 1 // header (Tag part) size s = "" switch b0 & 0xc0 { // Class 11000000 case 0: s += "Universal" case 0x40: s += "Application" case 0x80: s += "ContextSpecific" case 0xc0: s += "Private" } s += ":" switch b0 & 0x20 { // 00100000 case 0: s += "Primitive" primitive = true case 0x20: s += "Constructed" } iPrintf("identifier %s:%s", s, tags[b0 & 0x1f]) if isHexArg == false { Printf(" %v", secn[:secl+1]) } Printf("\n") n, m = derToLength(bs[h:]) // m is the size of length field if Dflag { iPrintf("ratio (%d + %d) / %d\n", m+h, n, len(bs)) } contents = bs[h+m:h+m+n] if primitive { switch b0 & 0x1f { case TagInteger: iPrintf("contents %x\n", contents) case TagUTF8String,TagIA5String, TagPrintableString, TagUTCTime: iPrintf("contents %s\n", string(contents)) case TagBitString: iPrintf("contents %x\n", contents) case TagOID: oid := derToOid(contents) dotOid := dotOid(oid) iPrintf("contents \"%s\" (%s)\n", dotOid, oidmap[dotOid]) default: iPrintf("contents %x\n", contents) } } offset = h+m+n if !primitive { indent += 2 parse(contents) indent -= 2 //iPrintf("contents %x\n", contents) } if Dflag { iPrintf("offset %d\n", offset) } return } func parse(bs []byte){ m := len(bs) var offset int var err error secl++ for n:=1; m > 0; n++ { secn[secl] = n offset, err = asn1Parse(bs) // NB: ":=" is NG if err != nil { Fprintln(os.Stderr, "# Parse error", offset) } bs = bs[offset:] m = len(bs) } secl-- } var Dflag bool func main() { var pb *pem.Block var err error var bs []byte var ok bool tagsInit() args := os.Args[1:] if len(args) == 0 { Error(usage) } if args[0] == "-D" { Dflag = true args = args[1:] } if len(args) != 1 { Error(usage) } arg := args[0] // path.Match(pattern, name string) is for globe match ok, err = path.Match("*.pem",arg) if err != nil { Error(err) } if !ok { isHexArg = true bs,err = hex.DecodeString(arg) if err != nil { Error(err) } parse(bs) return } bs,err = os.ReadFile(arg) if err != nil { Error(err) } var Bs []byte pb,bs = pem.Decode(bs) // pb is pem block for k:=0; pb != nil; k++ { Bs = append(Bs,pb.Bytes...) pb,bs = pem.Decode(bs) // pb is pem block } parse(Bs) } func oid_test() { var n = 33157 var m int Println(n) // 33157 bs := make([]byte,20) m = uintToDer(bs,n) Printf("%x\n",bs[0:m]) // 828305 Println(derToUint(bs)) // 33157 2 oid := []int {1, 21, 134} Println(oid) Printf("%x\n",addOidToDer(bs, oid)) oid = []int {1,3,6,1,4,1,343} Println(oid) bs = addOidToDer(bs, oid) Printf("%x\n", bs) Printf("%v\n",derToOid(bs)) } /* $GOROOT/src/encoding/asn1/common.go X.690 https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf */