Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Last active August 13, 2023 13:49
Show Gist options
  • Save salrashid123/bab3e6908468e18886ae94a87a9f757d to your computer and use it in GitHub Desktop.
Save salrashid123/bab3e6908468e18886ae94a87a9f757d to your computer and use it in GitHub Desktop.
Parse certificate.Issuer from raw DER bytes in golang
/*
Marshall Certificate.Issuer struct from raw DER Bytes
code uses parser from https://go.dev/src/crypto/x509/parser.go
https://lapo.it/asn1js/#MIIELTCCAxWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJVUzEPMA0GA1UECgwGR29vZ2xlMRMwEQYDVQQLDApFbnRlcnByaXNlMSIwIAYDVQQDDBlFbnRlcnByaXNlIFN1Ym9yZGluYXRlIENBMB4XDTIzMDQwNzE0MDQwN1oXDTI1MDQwNjE0MDQwN1owRTELMAkGA1UEBhMCVVMxDzANBgNVBAoMBkdvb2dsZTETMBEGA1UECwwKRW50ZXJwcmlzZTEQMA4GA1UEAwwHbWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGzSU8QxpblEH9igyDzn24R1M3dNU9inBjxPmGFrbzI1HN2oGxVdYSDmTmRwPmuLVxvX3HiFSGuhG3GvjrMskydY6dqvcZmOB8IMcCuw74kXIOevGyBVr8EJN-Z8tLXvZHyZgDe-1bDRkw4IsmhJrgnrWWAoWucyTSKYq8U5ZQt_1f3_nMAtkmt2kI3mrF1E_ibasa_aWngsyjtAVC-y1p2hDznHU8rDLxdgNKIo3X85eDFAOi-wDPMxrO3_vtNP2i1OrKv-GLj_0d1HzGV_4R5sMzNCOVXJ7H7TbbxFceC6ajMwEddZdASB7E4Mc43T4yuQy0_opravLkQQFacuZcCAwEAAaOCARQwggEQMA4GA1UdDwEB_wQEAwIHgDAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRNAL-pKqCVY-RHtsRYG80GoULfLDAfBgNVHSMEGDAWgBSTvRe8TcBkyWIHOosz4S12KzT3wzBEBggrBgEFBQcBAQQ4MDYwNAYIKwYBBQUHMAKGKGh0dHA6Ly9wa2kuZXNvZGVtb2FwcDIuY29tL2NhL3Rscy1jYS5jZXIwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3BraS5lc29kZW1vYXBwMi5jb20vY2EvdGxzLWNhLmNybDAdBgNVHREEFjAUghJtY2xpZW50LmRvbWFpbi5jb20wDQYJKoZIhvcNAQELBQADggEBAGhTCVz0uoezSbQxIa5zi8aLl2lYYQsFkBYOx-gBLhvRVUL6gg9pWWnDhrTqR0Wc4EQIvxSJuP_l4vhkSFwYq1Zae2Yg2ddIhQBiZTL5pgFNM82Nvre8GQ9IzKnIF9TBKwk0q55xPx7Tzz8MNQLRKUUePXcG1OsxBdsKcDG8EgV6Dk5Xx94A2q6qX5_yBtJe4UJhnJWBlD75oAP_2_UajlmVQGVi_KQ6EOf_zmm8U9mVjCFBatFZ34iGi-T8MxUUPUYDEJEIbnoLAbJwnFCfu94h7gI6b1eqtopWUkrSI1VmKh6_j2ce6zhkQ9EzNi5ClchS2vz2LqPrYiIHL7eDFEw
*/
package main
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"errors"
"flag"
"fmt"
"unicode"
"unicode/utf16"
"unicode/utf8"
"golang.org/x/crypto/cryptobyte"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
)
// https://oidref.com/2.5.29.29
// https://go.dev/src/crypto/x509/parser.go
/*
$ openssl x509 -in cert.pem -outform der | openssl asn1parse -inform der -i -strparse 31
0:d=0 hl=2 l= 87 cons: SEQUENCE
2:d=1 hl=2 l= 11 cons: SET
4:d=2 hl=2 l= 9 cons: SEQUENCE
6:d=3 hl=2 l= 3 prim: OBJECT :countryName
11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :US
15:d=1 hl=2 l= 15 cons: SET
17:d=2 hl=2 l= 13 cons: SEQUENCE
19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
24:d=3 hl=2 l= 6 prim: UTF8STRING :Google
32:d=1 hl=2 l= 19 cons: SET
34:d=2 hl=2 l= 17 cons: SEQUENCE
36:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
41:d=3 hl=2 l= 10 prim: UTF8STRING :Enterprise
53:d=1 hl=2 l= 34 cons: SET
55:d=2 hl=2 l= 32 cons: SEQUENCE
57:d=3 hl=2 l= 3 prim: OBJECT :commonName
62:d=3 hl=2 l= 25 prim: UTF8STRING :Enterprise Subordinate CA
$ openssl x509 -in cert.pem -outform der | openssl asn1parse -inform der -i -strparse 31 -noout -out issuer.raw
$ cat issuer.raw | xxd -p -c 100
3057310b3009060355040613025553310f300d060355040a0c06476f6f676c6531133011060355040b0c0a456e74657270726973653122302006035504030c19456e7465727072697365205375626f7264696e617465204341
*/
var (
IssuerOID = []int{2, 5, 29, 29}
hexOid = flag.String("hexOid", "3057310b3009060355040613025553310f300d060355040a0c06476f6f676c6531133011060355040b0c0a456e74657270726973653122302006035504030c19456e7465727072697365205375626f7264696e617465204341", "Der he oid")
)
func main() {
dd, err := hex.DecodeString(*hexOid)
if err != nil {
panic(err)
}
e := &x509.Certificate{
// RawIssuer: dd,
}
input := cryptobyte.String(dd)
var issuerSeq cryptobyte.String
if !input.ReadASN1Element(&issuerSeq, cryptobyte_asn1.SEQUENCE) {
panic(err)
}
issuerRDNs, err := parseName(issuerSeq)
if err != nil {
panic(err)
}
e.Issuer.FillFromRDNSequence(issuerRDNs)
fmt.Printf("Issuer CommonName: %s\n", e.Issuer.CommonName)
}
// from // https://go.dev/src/crypto/x509/parser.go
func parseName(raw cryptobyte.String) (*pkix.RDNSequence, error) {
if !raw.ReadASN1(&raw, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: invalid RDNSequence")
}
var rdnSeq pkix.RDNSequence
for !raw.Empty() {
var rdnSet pkix.RelativeDistinguishedNameSET
var set cryptobyte.String
if !raw.ReadASN1(&set, cryptobyte_asn1.SET) {
return nil, errors.New("x509: invalid RDNSequence")
}
for !set.Empty() {
var atav cryptobyte.String
if !set.ReadASN1(&atav, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: invalid RDNSequence: invalid attribute")
}
var attr pkix.AttributeTypeAndValue
if !atav.ReadASN1ObjectIdentifier(&attr.Type) {
return nil, errors.New("x509: invalid RDNSequence: invalid attribute type")
}
var rawValue cryptobyte.String
var valueTag cryptobyte_asn1.Tag
if !atav.ReadAnyASN1(&rawValue, &valueTag) {
return nil, errors.New("x509: invalid RDNSequence: invalid attribute value")
}
var err error
attr.Value, err = parseASN1String(valueTag, rawValue)
if err != nil {
return nil, fmt.Errorf("x509: invalid RDNSequence: invalid attribute value: %s", err)
}
rdnSet = append(rdnSet, attr)
}
rdnSeq = append(rdnSeq, rdnSet)
}
return &rdnSeq, nil
}
func parseASN1String(tag cryptobyte_asn1.Tag, value []byte) (string, error) {
switch tag {
case cryptobyte_asn1.T61String:
return string(value), nil
case cryptobyte_asn1.PrintableString:
for _, b := range value {
if !isPrintable(b) {
return "", errors.New("invalid PrintableString")
}
}
return string(value), nil
case cryptobyte_asn1.UTF8String:
if !utf8.Valid(value) {
return "", errors.New("invalid UTF-8 string")
}
return string(value), nil
case cryptobyte_asn1.Tag(asn1.TagBMPString):
if len(value)%2 != 0 {
return "", errors.New("invalid BMPString")
}
// Strip terminator if present.
if l := len(value); l >= 2 && value[l-1] == 0 && value[l-2] == 0 {
value = value[:l-2]
}
s := make([]uint16, 0, len(value)/2)
for len(value) > 0 {
s = append(s, uint16(value[0])<<8+uint16(value[1]))
value = value[2:]
}
return string(utf16.Decode(s)), nil
case cryptobyte_asn1.IA5String:
s := string(value)
if isIA5String(s) != nil {
return "", errors.New("invalid IA5String")
}
return s, nil
case cryptobyte_asn1.Tag(asn1.TagNumericString):
for _, b := range value {
if !('0' <= b && b <= '9' || b == ' ') {
return "", errors.New("invalid NumericString")
}
}
return string(value), nil
}
return "", fmt.Errorf("unsupported string type: %v", tag)
}
func isPrintable(b byte) bool {
return 'a' <= b && b <= 'z' ||
'A' <= b && b <= 'Z' ||
'0' <= b && b <= '9' ||
'\'' <= b && b <= ')' ||
'+' <= b && b <= '/' ||
b == ' ' ||
b == ':' ||
b == '=' ||
b == '?' ||
// This is technically not allowed in a PrintableString.
// However, x509 certificates with wildcard strings don't
// always use the correct string type so we permit it.
b == '*' ||
// This is not technically allowed either. However, not
// only is it relatively common, but there are also a
// handful of CA certificates that contain it. At least
// one of which will not expire until 2027.
b == '&'
}
func isIA5String(s string) error {
for _, r := range s {
// Per RFC5280 "IA5String is limited to the set of ASCII characters"
if r > unicode.MaxASCII {
return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s)
}
}
return nil
}
/*
$ openssl x509 -in cert.pem -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Google, OU = Enterprise, CN = Enterprise Subordinate CA
Validity
Not Before: Apr 7 14:04:07 2023 GMT
Not After : Apr 6 14:04:07 2025 GMT
Subject: C = US, O = Google, OU = Enterprise, CN = mclient
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b1:b3:49:4f:10:c6:96:e5:10:7f:62:83:20:f3:
9f:6e:11:d4:cd:dd:35:4f:62:9c:18:f1:3e:61:85:
ad:bc:c8:d4:73:76:a0:6c:55:75:84:83:99:39:91:
c0:f9:ae:2d:5c:6f:5f:71:e2:15:21:ae:84:6d:c6:
be:3a:cc:b2:4c:9d:63:a7:6a:bd:c6:66:38:1f:08:
31:c0:ae:c3:be:24:5c:83:9e:bc:6c:81:56:bf:04:
24:df:99:f2:d2:d7:bd:91:f2:66:00:de:fb:56:c3:
46:4c:38:22:c9:a1:26:b8:27:ad:65:80:a1:6b:9c:
c9:34:8a:62:af:14:e5:94:2d:ff:57:f7:fe:73:00:
b6:49:ad:da:42:37:9a:b1:75:13:f8:9b:6a:c6:bf:
69:69:e0:b3:28:ed:01:50:be:cb:5a:76:84:3c:e7:
1d:4f:2b:0c:bc:5d:80:d2:88:a3:75:fc:e5:e0:c5:
00:e8:be:c0:33:cc:c6:b3:b7:fe:fb:4d:3f:68:b5:
3a:b2:af:f8:62:e3:ff:47:75:1f:31:95:ff:84:79:
b0:cc:cd:08:e5:57:27:b1:fb:4d:b6:f1:15:c7:82:
e9:a8:cc:c0:47:5d:65:d0:12:07:b1:38:31:ce:37:
4f:8c:ae:43:2d:3f:a2:9a:da:bc:b9:10:40:56:9c:
b9:97
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature
X509v3 Basic Constraints:
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Subject Key Identifier:
4D:00:BF:A9:2A:A0:95:63:E4:47:B6:C4:58:1B:CD:06:A1:42:DF:2C
X509v3 Authority Key Identifier:
93:BD:17:BC:4D:C0:64:C9:62:07:3A:8B:33:E1:2D:76:2B:34:F7:C3
Authority Information Access:
CA Issuers - URI:http://pki.esodemoapp2.com/ca/tls-ca.cer
X509v3 CRL Distribution Points:
Full Name:
URI:http://pki.esodemoapp2.com/ca/tls-ca.crl
X509v3 Subject Alternative Name:
DNS:mclient.domain.com
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
68:53:09:5c:f4:ba:87:b3:49:b4:31:21:ae:73:8b:c6:8b:97:
69:58:61:0b:05:90:16:0e:c7:e8:01:2e:1b:d1:55:42:fa:82:
0f:69:59:69:c3:86:b4:ea:47:45:9c:e0:44:08:bf:14:89:b8:
ff:e5:e2:f8:64:48:5c:18:ab:56:5a:7b:66:20:d9:d7:48:85:
00:62:65:32:f9:a6:01:4d:33:cd:8d:be:b7:bc:19:0f:48:cc:
a9:c8:17:d4:c1:2b:09:34:ab:9e:71:3f:1e:d3:cf:3f:0c:35:
02:d1:29:45:1e:3d:77:06:d4:eb:31:05:db:0a:70:31:bc:12:
05:7a:0e:4e:57:c7:de:00:da:ae:aa:5f:9f:f2:06:d2:5e:e1:
42:61:9c:95:81:94:3e:f9:a0:03:ff:db:f5:1a:8e:59:95:40:
65:62:fc:a4:3a:10:e7:ff:ce:69:bc:53:d9:95:8c:21:41:6a:
d1:59:df:88:86:8b:e4:fc:33:15:14:3d:46:03:10:91:08:6e:
7a:0b:01:b2:70:9c:50:9f:bb:de:21:ee:02:3a:6f:57:aa:b6:
8a:56:52:4a:d2:23:55:66:2a:1e:bf:8f:67:1e:eb:38:64:43:
d1:33:36:2e:42:95:c8:52:da:fc:f6:2e:a3:eb:62:22:07:2f:
b7:83:14:4c
-----BEGIN CERTIFICATE-----
MIIELTCCAxWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJVUzEP
MA0GA1UECgwGR29vZ2xlMRMwEQYDVQQLDApFbnRlcnByaXNlMSIwIAYDVQQDDBlF
bnRlcnByaXNlIFN1Ym9yZGluYXRlIENBMB4XDTIzMDQwNzE0MDQwN1oXDTI1MDQw
NjE0MDQwN1owRTELMAkGA1UEBhMCVVMxDzANBgNVBAoMBkdvb2dsZTETMBEGA1UE
CwwKRW50ZXJwcmlzZTEQMA4GA1UEAwwHbWNsaWVudDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBALGzSU8QxpblEH9igyDzn24R1M3dNU9inBjxPmGFrbzI
1HN2oGxVdYSDmTmRwPmuLVxvX3HiFSGuhG3GvjrMskydY6dqvcZmOB8IMcCuw74k
XIOevGyBVr8EJN+Z8tLXvZHyZgDe+1bDRkw4IsmhJrgnrWWAoWucyTSKYq8U5ZQt
/1f3/nMAtkmt2kI3mrF1E/ibasa/aWngsyjtAVC+y1p2hDznHU8rDLxdgNKIo3X8
5eDFAOi+wDPMxrO3/vtNP2i1OrKv+GLj/0d1HzGV/4R5sMzNCOVXJ7H7TbbxFceC
6ajMwEddZdASB7E4Mc43T4yuQy0/opravLkQQFacuZcCAwEAAaOCARQwggEQMA4G
A1UdDwEB/wQEAwIHgDAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0G
A1UdDgQWBBRNAL+pKqCVY+RHtsRYG80GoULfLDAfBgNVHSMEGDAWgBSTvRe8TcBk
yWIHOosz4S12KzT3wzBEBggrBgEFBQcBAQQ4MDYwNAYIKwYBBQUHMAKGKGh0dHA6
Ly9wa2kuZXNvZGVtb2FwcDIuY29tL2NhL3Rscy1jYS5jZXIwOQYDVR0fBDIwMDAu
oCygKoYoaHR0cDovL3BraS5lc29kZW1vYXBwMi5jb20vY2EvdGxzLWNhLmNybDAd
BgNVHREEFjAUghJtY2xpZW50LmRvbWFpbi5jb20wDQYJKoZIhvcNAQELBQADggEB
AGhTCVz0uoezSbQxIa5zi8aLl2lYYQsFkBYOx+gBLhvRVUL6gg9pWWnDhrTqR0Wc
4EQIvxSJuP/l4vhkSFwYq1Zae2Yg2ddIhQBiZTL5pgFNM82Nvre8GQ9IzKnIF9TB
Kwk0q55xPx7Tzz8MNQLRKUUePXcG1OsxBdsKcDG8EgV6Dk5Xx94A2q6qX5/yBtJe
4UJhnJWBlD75oAP/2/UajlmVQGVi/KQ6EOf/zmm8U9mVjCFBatFZ34iGi+T8MxUU
PUYDEJEIbnoLAbJwnFCfu94h7gI6b1eqtopWUkrSI1VmKh6/j2ce6zhkQ9EzNi5C
lchS2vz2LqPrYiIHL7eDFEw=
-----END CERTIFICATE-----
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment