Last active
August 13, 2023 13:49
-
-
Save salrashid123/bab3e6908468e18886ae94a87a9f757d to your computer and use it in GitHub Desktop.
Parse certificate.Issuer from raw DER bytes in golang
This file contains hidden or 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
/* | |
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