skip to content

Identity and Credentials

The secret material a process holds: certificates parsed from PEM into x509, signing keys swapped while traffic flows, credentials read from the environment. Interviewers ask where each came from.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

18

What does x509.CreateCertificate return in Go, and what must you do before writing a .crt file?

level: juniorimportance: must knowfreq 40%

answer

  1. the function returns bytes, not a file
  2. binary first, text second
  3. encoding/pem does the wrapping
  4. the block Type string matters
  5. pub is the subject's, priv is the signer's

basics

~10 s

x509.CreateCertificate returns the signed certificate as raw DER bytes, not a file and not text. To get a .crt or .pem file, wrap those bytes with encoding/pem in a block whose Type is CERTIFICATE.

solid answer

~40 s

The signature is `CreateCertificate(rand io.Reader, template, parent *x509.Certificate, pub, priv any) ([]byte, error)`. The `[]byte` it returns is the DER encoding of the finished, signed certificate — binary ASN.1, not something you can `fmt.Println` usefully. Everything that reads certificates from disk in the Go world expects PEM, so you pass the DER to `pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: der})`, or `pem.EncodeToMemory` if you want the bytes back. The two other arguments people get wrong are `pub` and `priv`: `pub` is the *subject's* public key (the key that goes into the certificate) and `priv` is the *signer's* private key. The matching private key is written as a separate PEM file, typically `x509.MarshalPKCS8PrivateKey` under the block type `PRIVATE KEY`.

code

go · 6 lines
go
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key)
if err != nil {
	return err
}
// der is raw DER; a .crt file is that wrapped in PEM armour
return pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der})

go deeper

for a junior

Remember the shape: fill a template, call x509.CreateCertificate, get DER bytes, wrap them with pem.Encode using the block type CERTIFICATE. Also remember that the public key argument belongs to the subject and the private key argument belongs to the issuer.

for a middle

Be ready to explain what DER and PEM actually are, why the ecosystem exchanges the text form, and how the private key is serialised and stored as a separate PKCS#8 PEM file rather than inside the certificate.

for a senior

Show that you treat the issued material as an artefact with a lifecycle: file permissions on the key, chain ordering when several blocks share a file, and a test that reads back what you wrote instead of trusting that the bytes were fine.

for a principal

Frame it as an interface question — what format your platform hands to consumers, whether keys are ever generated centrally or only ever on the machine that will use them, and what that choice costs you in audit and blast radius.

## The one function that issues a certificate In Go, minting a certificate is a single standard-library call: ``` func x509.CreateCertificate(rand io.Reader, template, parent *x509.Certificate, pub, priv any) ([]byte, error) ``` There is no builder, no config file and no side effect on disk. You hand it five things and it hands you bytes. - `rand` is a source of randomness for the signature; in practice always `crypto/rand.Reader`. - `template` is an `*x509.Certificate` you fill in yourself. It is used as a *description of what to issue* — serial number, subject, validity window, key usages, SAN entries — not as a parsed certificate. - `parent` is the issuing certificate. If you pass the same value you passed as `template`, the result is self-signed. - `pub` is the public key that will be embedded in the certificate — the key belonging to whoever the certificate is *about*. - `priv` is the private key that signs it — the key belonging to the *issuer*. Swapping `pub` and `priv` is the classic first-day mistake. When a root signs a leaf, `pub` is the leaf's public key and `priv` is the root's private key; they belong to two different key pairs, and nothing in the type signature stops you mixing them up because both parameters are `any`. ## DER, and why it is not the file you want The return value is **DER**: Distinguished Encoding Rules, the canonical binary form of the ASN.1 structure that an X.509 certificate is. It is a compact byte string. It is perfectly valid to write it straight to a `.der` or `.cer` file, and `x509.ParseCertificate` reads exactly this form. But nearly every tool, config file and Kubernetes-free deployment pipeline in the ecosystem exchanges certificates as **PEM**: the DER bytes base64-encoded, split into 64-character lines, and wrapped in `-----BEGIN CERTIFICATE-----` / `-----END CERTIFICATE-----` armour. PEM's advantage is that it is text — it survives environment variables, YAML, copy-paste and shell heredocs — and that several certificates can be concatenated into one file to ship a chain. The conversion lives in `encoding/pem`: ``` pem.Encode(w io.Writer, b *pem.Block) error pem.EncodeToMemory(b *pem.Block) []byte ``` A `pem.Block` has a `Type string`, optional `Headers map[string]string`, and `Bytes []byte`. For a certificate the type string is exactly `CERTIFICATE`. That label is not decoration: parsers switch on it, and a block labelled `PUBLIC KEY` or `CERTIFICATE REQUEST` holding certificate DER will be rejected by anything careful. ## The private key is a separate artefact `CreateCertificate` never touches the subject's private key — it only needs the public half. Whoever generated the key pair keeps the private key, and if the issuing service generated it on the requester's behalf it must serialise it separately: `x509.MarshalPKCS8PrivateKey(key)` produces DER for essentially any key type, and it goes in a PEM block of type `PRIVATE KEY`. (`x509.MarshalECPrivateKey` and the legacy `RSA PRIVATE KEY` block type still exist; PKCS#8 is the modern default.) A TLS server then loads the pair with `tls.X509KeyPair(certPEM, keyPEM)`, which is why the two files are conventionally kept side by side but never concatenated into one block. ## Errors you will actually hit `CreateCertificate` returns an error rather than panicking, and the most common one on a first attempt is a missing serial number: the template's `SerialNumber` field is a `*big.Int` with no default, and the call fails outright if it is nil. Others come from the key material — a `pub` that is not a supported public key type, or a `priv` whose type cannot sign. ## Shape of a correct issue-and-write Fill a template, call `CreateCertificate`, check the error, then `pem.Encode` the DER. Four steps, in that order. Two mistakes are worth naming: base64-encoding the DER yourself before handing it to `pem.Encode` (you get double-encoded garbage that decodes to nonsense), and naming a DER file `.pem` (nothing will read it, and the error messages are unhelpful because the parser sees no armour and gives up immediately).

  • What are the last two arguments to x509.CreateCertificate, and what breaks if you swap them?
    `pub` is the public key of the entity the certificate is about; `priv` is the private key of the issuer that signs it. Both are declared `any`, so a swap compiles. At best you get an error about an unsupported key type; at worst you issue a certificate that embeds the CA's public key and is signed by the leaf's key, which nothing in your chain will accept.
  • How do you write the matching private key alongside the certificate?
    Serialise it separately: `x509.MarshalPKCS8PrivateKey(key)` gives DER, which you wrap in a PEM block of type `PRIVATE KEY`. Keep it in its own file with restrictive permissions — `tls.X509KeyPair` takes the certificate PEM and the key PEM as two separate byte slices.
  • Can one PEM file hold more than one certificate?
    Yes. PEM blocks concatenate, so a leaf followed by its intermediates in one file is the normal way to ship a chain. Order matters for most consumers: leaf first, then each issuer upward. The root is usually omitted because the verifier is expected to already trust it.

saying these in an interview costs you the question

  • Thinks x509.CreateCertificate writes a file to disk
  • Base64-encodes the DER before handing it to pem.Encode
  • Passes the signer's private key where the subject's public key belongs
  • Names DER output .pem and wonders why parsers reject it
  • Puts certificate DER in a PEM block labelled PUBLIC KEY
open as a page

In Go, how do you tell an unset environment variable from one set to an empty string when loading a credential?

level: juniorimportance: must knowfreq 70%

basics

~20 s

os.Getenv returns an empty string for both cases, so it cannot tell them apart. os.LookupEnv returns the value plus a boolean that is false only when the variable is absent, letting you fail startup instead of running with an empty credential.

open as a page

In Go, how do you turn the bytes of a PEM certificate file into an *x509.Certificate?

level: juniorimportance: must knowfreq 48%

basics

~10 s

Call pem.Decode on the file bytes, check the returned block is non-nil and its Type is CERTIFICATE, then pass block.Bytes, which is the DER, to x509.ParseCertificate. Both steps can fail; check both.

open as a page

Why must a certificate you issue in Go set DNSNames rather than only Subject.CommonName?

level: middleimportance: must knowfreq 44%

basics

~20 s

Hostname checking reads the Subject Alternative Name extension, which the template fills from DNSNames, IPAddresses, URIs and EmailAddresses. Go's verifier stopped matching Subject.CommonName in Go 1.15, so a certificate carrying the hostname only in CommonName is rejected.

open as a page

A Go reload goroutine assigns a new *tls.Certificate that GetCertificate reads — why does -race flag it?

level: middleimportance: must knowfreq 50%

basics

~20 s

Two goroutines touch one variable with no synchronisation: the reloader writes the pointer while handshake goroutines read it. A pointer-sized write is not automatically ordered in Go. Keep the certificate in an atomic.Pointer and Load it inside the callback.

open as a page

What does calling Verify on an *x509.Certificate with x509.VerifyOptions check, and what does it not?

level: middleimportance: must knowfreq 44%

basics

~20 s

Verify builds chains from that certificate to a root in opts.Roots, using opts.Intermediates as available links, and checks signatures, validity dates, CA constraints, extended key usage and, when DNSName is set, the hostname. It never checks revocation.

open as a page

Why does a Go TLS server use tls.Config.GetCertificate rather than Certificates to pick up a renewed key pair?

level: juniorimportance: should knowfreq 40%

basics

~20 s

tls.Config.GetCertificate is a callback the server consults during each incoming handshake, so a renewed key pair takes effect on the next connection. The Certificates field is read when the server starts, so replacing the files on disk changes nothing until a restart.

open as a page

In x509.CreateCertificate, how do you self-sign rather than sign with a CA parent?

level: middleimportance: should knowfreq 38%

basics

~20 s

Pass the same template value as both the template and the parent argument, and sign with the private key matching the certificate's own public key. Any other parent means that parent's private key signs, and its Subject becomes the new certificate's Issuer.

open as a page

Why can fmt's %+v still print a secret field whose type has a String method?

level: middleimportance: should knowfreq 52%

basics

~20 s

fmt reaches struct fields by reflection and can only call String on a field it can take as an interface value. Unexported fields cannot be, a pointer-receiver String is not in a value's method set, and %#v ignores String entirely.

open as a page

x509.CreateCertificate produced a CA whose signatures verifiers reject as unauthorized — which template fields were unset?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Almost always BasicConstraintsValid, which must be true or IsCA is never encoded, so verifiers see an ordinary end-entity certificate. The issuer's KeyUsage must also include x509.KeyUsageCertSign whenever KeyUsage is set at all.

open as a page

With exec.Cmd, how do you keep a loaded secret out of the child process's environment?

level: seniorimportance: should knowfreq 45%

basics

~20 s

If Cmd.Env is nil the child inherits the parent's entire environment, secrets included. Set Cmd.Env explicitly to only what the child needs — filter a copy from Cmd.Environ() — or pass a file path rather than the value.

open as a page

Verify on a customer's PEM chain returns 'certificate signed by unknown authority' — how do you find the missing link?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Split the bundle into PEM blocks, parse each, and print Subject, Issuer, NotAfter and DNSNames. The break is where a certificate's Issuer matches no other's Subject and no trusted root; Go never fetches a missing issuer.

open as a page

How do you decide between hot-reloading TLS key material through GetCertificate and rolling restarts fleet-wide?

level: principalimportance: should knowfreq 35%

basics

~20 s

Decide on what a restart costs you: long-lived connections and per-process resumption state, versus the maintenance cost of swap code that runs rarely. Pick one posture per fleet, exercise it continuously, and alert on remaining validity rather than on reload success.

open as a page

What Go-side secret-handling convention do you mandate fleet-wide, and what do you leave to teams?

level: principalimportance: should knowfreq 38%

basics

~20 s

Mandate the two things that fail silently otherwise: secrets arrive as file contents read with os.ReadFile rather than as environment values, and every secret is a named type whose String method masks it. Leave storage backends and per-service layout to teams.

open as a page

What are the rules for a certificate template's SerialNumber in Go, and how should you generate one?

level: middleimportance: nice to knowfreq 26%

basics

~10 s

SerialNumber is a *big.Int with no default: x509.CreateCertificate fails if it is nil. It must be positive, at most 20 octets, and unique per issuer. Generate 128 random bits with crypto/rand.Int rather than counting.

open as a page

What does the builtin clear(b) do to a []byte, and which copies of those bytes does it miss?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

clear(b) writes the zero value over every element from index 0 to len(b)-1, leaving len and cap unchanged; on a map it deletes all entries. It reaches only that one array, so copies made by conversions or append survive.

open as a page

What does the bool returned by x509.CertPool.AppendCertsFromPEM actually tell you?

level: middleimportance: nice to knowfreq 31%

basics

~20 s

It reports whether at least one certificate in the PEM input was parsed and added, not whether all were. A bundle where most blocks are corrupt still returns true, so it is not a validity check.

open as a page

Why do Go TLS servers behind one load balancer need tls.Config.SetSessionTicketKeys given identical keys?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

By default each Go process generates and rotates its own session ticket key, so a ticket issued by one replica cannot be decrypted by another and the client pays a full handshake instead. SetSessionTicketKeys gives every replica the same key set.

open as a page