shadow1ng/fscan · error

empty certificate chain

Error message

empty certificate chain

What it means

X509CertificateChain.GetPublicKey extracts the RSA public key from the server certificate chain carried in the CONNECT_RESPONSE. If the CertBlobArray is empty there is no certificate to parse, so this error is returned. This typically means the server sent no certificate chain where one was required for the chosen security path.

Source

Thrown at libs/grdp/protocol/t125/gcc/gcc.go:410

	p.SignatureBlob, _ = core.ReadBytes(int(p.SignatureBlobLen)-8, r)
	p.Padding, _ = core.ReadBytes(8, r)

	return nil
}

type CertBlob struct {
	CbCert uint32 `struc:"little,sizeof=AbCert"`
	AbCert []byte `struc:"little"`
}
type X509CertificateChain struct {
	NumCertBlobs  uint32     `struc:"little,sizeof=CertBlobArray"`
	CertBlobArray []CertBlob `struc:"little"`
	Padding       []byte     `struc:"[12]byte"`
}

func (x *X509CertificateChain) GetPublicKey() (*rsa.PublicKey, error) {
	if len(x.CertBlobArray) == 0 {
		return nil, errors.New("empty certificate chain")
	}
	data := x.CertBlobArray[len(x.CertBlobArray)-1].AbCert
	cert, err := x509.ParseCertificate(data)
	if err != nil {
		return nil, fmt.Errorf("parse certificate: %w", err)
	}
	if cert.PublicKey == nil {
		var pubKeyInfo struct {
			Algorithm        pkix.AlgorithmIdentifier
			SubjectPublicKey asn1.BitString
		}
		_, err = asn1.Unmarshal(cert.RawSubjectPublicKeyInfo, &pubKeyInfo)
		if err != nil {
			return nil, fmt.Errorf("unmarshal public key info: %w", err)
		}
		rsaPublicKey, err := x509.ParsePKCS1PublicKey(pubKeyInfo.SubjectPublicKey.Bytes)
		if err != nil {
			return nil, fmt.Errorf("parse PKCS1 public key: %w", err)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Verify the server's selected security protocol actually returns an X.509 certificate chain; if the server uses NLA/CredSSP, this code path may not apply — use the matching security handler
  2. Hex-dump the certificate PDU and check certCount: if nonzero, fix the struc decoding (alignment/endianness) of CertBlobArray
  3. Handle the alternate certificate types (SSCert/ProprietaryCert) instead of assuming X509CertificateChain
  4. Update grdp or the server config so standard TLS/RDP security with a real certificate chain is negotiated

Example fix

// before
if len(x.CertBlobArray) == 0 {
    return nil, errors.New("empty certificate chain")
}

// after
if len(x.CertBlobArray) == 0 {
    return nil, fmt.Errorf("empty certificate chain: server sent certCount=0 (security proto %v may not use X.509 certs)", selectedProto)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// check the certificate chain before extracting the key
func hasCertChain(x *gcc.X509CertificateChain) bool {
    return x != nil && len(x.CertBlobArray) > 0 && len(x.CertBlobArray[len(x.CertBlobArray)-1].AbCert) > 0
}

Type guard

func validCertChain(x *gcc.X509CertificateChain) bool {
    return x != nil && len(x.CertBlobArray) > 0
}

Try / catch

pub, err := certChain.GetPublicKey()
if err != nil {
    if strings.Contains(err.Error(), "empty certificate chain") {
        // server did not send an X.509 chain; switch security handler or abort
        return ErrNoServerCertificate
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetPublicKey on an X509CertificateChain parsed from a server_proposals/certificate blob whose CertBlobArray decoded to zero entries — e.g. the server chose a security path without sending an X.509 chain, or the struc decoding silently produced an empty array.

Common situations: Servers configured for non-standard security (e.g. hybird/NLA-only) that omit the server certificate in the expected structure; decoding desync causing the array length field to read as 0; connecting to gateways that use different certificate PDU types (e.g. proprietary/bogus certs) this struct does not capture.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/4d461feb4435b4eb. Report an issue: GitHub.