[{"data":1,"prerenderedAt":524},["ShallowReactive",2],{"nav-categories":3,"tag-tls":8},{"pinned":4,"overflow":7},[5,6],"Technology","Security",[],[9],{"id":10,"title":11,"body":12,"category":6,"date":509,"description":510,"extension":511,"image":512,"imageCredit":513,"imageCreditUrl":513,"meta":514,"navigation":225,"path":516,"public":225,"seo":517,"stem":518,"tags":519,"__hash__":523},"posts\u002Fblog\u002Fgenerating-tls-certificates-openssl.md","Generating TLS certificates for server HTTPS",{"type":13,"value":14,"toc":495},"minimark",[15,19,30,38,41,46,49,91,98,112,116,119,128,131,136,245,248,257,261,264,289,292,321,330,334,337,340,345,348,363,367,370,373,379,424,444,448,451,460,464,468,471,480,484,491],[16,17,18],"p",{},"There are a handful of situations where generating your own TLS certificates\nbecomes genuinely useful. Perhaps you need HTTPS on a local development server\nto test secure cookies or service workers. Maybe you are integrating with a\nthird-party API that refuses plain HTTP callbacks. Or you simply want to\nunderstand how the certificate machinery works beneath the abstractions that\ntools like Let's Encrypt have made invisible.",[16,20,21,22,29],{},"This exercise walks through the process of creating self-signed TLS certificates\nusing ",[23,24,28],"a",{"href":25,"rel":26},"https:\u002F\u002Fwww.openssl.org",[27],"nofollow","OpenSSL"," utilities. I have used this\nstraightforward approach many times to quickly set up HTTPS on local development\nservers and even launch React and Vue applications over HTTPS to fully test end\nto end before deployments.",[16,31,32,33,37],{},"The foundational concepts here overlap with asymmetric cryptography applications\nsuch as sign and verify using RSA, as covered in my other post on\n",[23,34,36],{"href":35},"\u002Fblog\u002Frsa-jwt-nodejs","JWT with RSA",". TLS applications differ slightly in\ncontext from simple PKI key pairs because certificates bundle identity\ninformation alongside the public key, tying a cryptographic key to a specific\ndomain or organisation.",[16,39,40],{},"The whole process revolves around public key infrastructure (PKI), a system of\ncertificate authorities, certificates, and policies that establish trust. At the\nheart of it all sits a private key, which is where we begin.",[42,43,45],"h2",{"id":44},"creating-a-private-key","Creating a Private Key",[16,47,48],{},"The private key is the root of trust for everything that follows. It never\nleaves your machine, and every certificate you generate will be mathematically\nbound to it.",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-shell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# With password protection (encrypted key)\nopenssl genrsa -aes256 -out private.key 2048\n# Run one or the other!\n# Without password protection (unencrypted key)\nopenssl genrsa -out private.key 2048\n","shell","",[57,58,59,67,73,79,85],"code",{"__ignoreMap":55},[60,61,64],"span",{"class":62,"line":63},"line",1,[60,65,66],{},"# With password protection (encrypted key)\n",[60,68,70],{"class":62,"line":69},2,[60,71,72],{},"openssl genrsa -aes256 -out private.key 2048\n",[60,74,76],{"class":62,"line":75},3,[60,77,78],{},"# Run one or the other!\n",[60,80,82],{"class":62,"line":81},4,[60,83,84],{},"# Without password protection (unencrypted key)\n",[60,86,88],{"class":62,"line":87},5,[60,89,90],{},"openssl genrsa -out private.key 2048\n",[16,92,93,94,97],{},"The ",[57,95,96],{},"-aes256"," flag encrypts the key file with a passphrase. This is useful for\nproduction keys but can be cumbersome during local development where you may be\nrestarting servers frequently. For throwaway development certificates, the\nunencrypted variant is perfectly acceptable.",[99,100,101],"blockquote",{},[16,102,103,104,107,108,111],{},"It is advisable to observe best practices here when dealing with private keys\nsuch as file permissions on the local machine. Do not allow private key access\nto public. For group, it depends on the context. Consider using ",[57,105,106],{},"chmod 600","\nfile permissions even for testing. I also prefer to use\n",[57,109,110],{},"\u002Ftmp\u002Fpath-to-nuke-eventually"," and ensure things are nuked appropriately when\nwrapping up to avoid little leaks and general clutter lying around\ndirectories.",[42,113,115],{"id":114},"creating-a-certificate-signing-request","Creating a Certificate Signing Request",[16,117,118],{},"With the private key in hand, we now need to create a certificate signing\nrequest (CSR). A CSR is essentially a formal application for a certificate. It\ncontains your identity details and your public key, packaged together and signed\nby your private key to prove ownership. In a real-world scenario, you would send\nthis CSR to a certificate authority (CA) who would verify your identity and\nissue a signed certificate in return.",[50,120,122],{"className":52,"code":121,"language":54,"meta":55,"style":55},"openssl req -key private.key -new -out domain.csr\n",[57,123,124],{"__ignoreMap":55},[60,125,126],{"class":62,"line":63},[60,127,121],{},[16,129,130],{},"At this point you will be prompted to provide a series of values such as\ncountry, region, organisation, and so on. These fields form what is known as the\nDistinguished Name (DN) of the certificate.",[99,132,133],{},[16,134,135],{},"Common Name and FQDN are the primary fields of concern here. The Common Name\nshould match the domain you intend to serve traffic on.",[50,137,139],{"className":52,"code":138,"language":54,"meta":55,"style":55},"You are about to be asked to enter information that will be incorporated\ninto your certificate request.\nWhat you are about to enter is what is called a Distinguished Name or a DN.\nThere are quite a few fields but you can leave some blank\nFor some fields there will be a default value,\nIf you enter '.', the field will be left blank.\n-----\nCountry Name (2 letter code) []:ME\nState or Province Name (full name) []:Mordor\nLocality Name (eg, city) []:Barad Dur\nOrganization Name (eg, company) []:Maiar\nOrganizational Unit Name (eg, section) []:Sauron\nCommon Name (eg, fully qualified host name) []:mydomain.com\nEmail Address []:\n\nPlease enter the following 'extra' attributes\nto be sent with your certificate request\nA challenge password []:\n",[57,140,141,146,151,156,161,166,172,178,184,190,196,202,208,214,220,227,233,239],{"__ignoreMap":55},[60,142,143],{"class":62,"line":63},[60,144,145],{},"You are about to be asked to enter information that will be incorporated\n",[60,147,148],{"class":62,"line":69},[60,149,150],{},"into your certificate request.\n",[60,152,153],{"class":62,"line":75},[60,154,155],{},"What you are about to enter is what is called a Distinguished Name or a DN.\n",[60,157,158],{"class":62,"line":81},[60,159,160],{},"There are quite a few fields but you can leave some blank\n",[60,162,163],{"class":62,"line":87},[60,164,165],{},"For some fields there will be a default value,\n",[60,167,169],{"class":62,"line":168},6,[60,170,171],{},"If you enter '.', the field will be left blank.\n",[60,173,175],{"class":62,"line":174},7,[60,176,177],{},"-----\n",[60,179,181],{"class":62,"line":180},8,[60,182,183],{},"Country Name (2 letter code) []:ME\n",[60,185,187],{"class":62,"line":186},9,[60,188,189],{},"State or Province Name (full name) []:Mordor\n",[60,191,193],{"class":62,"line":192},10,[60,194,195],{},"Locality Name (eg, city) []:Barad Dur\n",[60,197,199],{"class":62,"line":198},11,[60,200,201],{},"Organization Name (eg, company) []:Maiar\n",[60,203,205],{"class":62,"line":204},12,[60,206,207],{},"Organizational Unit Name (eg, section) []:Sauron\n",[60,209,211],{"class":62,"line":210},13,[60,212,213],{},"Common Name (eg, fully qualified host name) []:mydomain.com\n",[60,215,217],{"class":62,"line":216},14,[60,218,219],{},"Email Address []:\n",[60,221,223],{"class":62,"line":222},15,[60,224,226],{"emptyLinePlaceholder":225},true,"\n",[60,228,230],{"class":62,"line":229},16,[60,231,232],{},"Please enter the following 'extra' attributes\n",[60,234,236],{"class":62,"line":235},17,[60,237,238],{},"to be sent with your certificate request\n",[60,240,242],{"class":62,"line":241},18,[60,243,244],{},"A challenge password []:\n",[16,246,247],{},"We can verify the CSR was created correctly by inspecting it. This is a good\nhabit to develop because it lets you catch mistakes before submitting to a CA or\nproceeding to signing.",[50,249,251],{"className":52,"code":250,"language":54,"meta":55,"style":55},"openssl req -text -verify -noout -in domain.csr\n",[57,252,253],{"__ignoreMap":55},[60,254,255],{"class":62,"line":63},[60,256,250],{},[42,258,260],{"id":259},"creating-a-self-signed-certificate","Creating a Self-Signed Certificate",[16,262,263],{},"A self-signed certificate is one where the issuer and the subject are the same\nentity. There is no third party vouching for your identity, which means browsers\nwill display a warning, but it is perfectly sufficient for local development and\ninternal tooling.",[50,265,267],{"className":52,"code":266,"language":54,"meta":55,"style":55},"$ openssl x509 -key private.key -in domain.csr -req -days 30 -out self-signed.crt\nSignature ok\nsubject=\u002FC=ME\u002FST=Mordor\u002FL=Barad Dur\u002FO=Maiar\u002FOU=Sauron\u002FCN=mydomain.com\nGetting Private key\n",[57,268,269,274,279,284],{"__ignoreMap":55},[60,270,271],{"class":62,"line":63},[60,272,273],{},"$ openssl x509 -key private.key -in domain.csr -req -days 30 -out self-signed.crt\n",[60,275,276],{"class":62,"line":69},[60,277,278],{},"Signature ok\n",[60,280,281],{"class":62,"line":75},[60,282,283],{},"subject=\u002FC=ME\u002FST=Mordor\u002FL=Barad Dur\u002FO=Maiar\u002FOU=Sauron\u002FCN=mydomain.com\n",[60,285,286],{"class":62,"line":81},[60,287,288],{},"Getting Private key\n",[16,290,291],{},"Let's inspect the certificate. The output should contain header information like\nthe following. Note the issuer and subject in this case will be identical\nbecause we signed the certificate with the same key that generated the CSR.",[50,293,295],{"className":52,"code":294,"language":54,"meta":55,"style":55},"$ openssl x509 -text -noout -in self-signed.crt\nCertificate:\n    Data:\n        ...\n        ...\n",[57,296,297,302,307,312,317],{"__ignoreMap":55},[60,298,299],{"class":62,"line":63},[60,300,301],{},"$ openssl x509 -text -noout -in self-signed.crt\n",[60,303,304],{"class":62,"line":69},[60,305,306],{},"Certificate:\n",[60,308,309],{"class":62,"line":75},[60,310,311],{},"    Data:\n",[60,313,314],{"class":62,"line":81},[60,315,316],{},"        ...\n",[60,318,319],{"class":62,"line":87},[60,320,316],{},[99,322,323],{},[16,324,325,326,329],{},"If we want to time-bound our certificate we should apply the ",[57,327,328],{},"-days"," argument.\nTypically, this is set to 365 days, however shorter lifetimes are a stronger\nsecurity practice. Automate certificate rotations from day one and apply a 30\nor 90 day expiration. Short-lived certificates limit the blast radius if a key\nis ever compromised.",[42,331,333],{"id":332},"creating-a-certificate-signed-with-our-certificate-authority","Creating a Certificate Signed With Our Certificate Authority",[16,335,336],{},"Self-signed certificates work in a pinch, but they have a significant\nlimitation: every individual certificate must be trusted manually. A better\napproach for local development is to create your own certificate authority (CA),\ntrust its root certificate once, and then any certificate it issues will\nautomatically be trusted by your browser.",[16,338,339],{},"Note that this CA will not be trusted on any devices by default since it is not\npart of the public trust hierarchy. However, if you are the sole user of your\napplication (entirely plausible for local development servers), trusting this\nroot CA once on your machine grants all issued certificates validity in your\nbrowser without repeated security prompts.",[341,342,344],"h3",{"id":343},"creating-our-root-ca","Creating Our Root CA",[16,346,347],{},"The root CA needs its own key pair and a self-signed certificate. We give it a\nlong validity period (10 years here) since rotating a root CA means re-issuing\nevery certificate it has signed.",[50,349,351],{"className":52,"code":350,"language":54,"meta":55,"style":55},"openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -noenc \\\n    -keyout my-ca-root.key -out my-ca-root.crt\n",[57,352,353,358],{"__ignoreMap":55},[60,354,355],{"class":62,"line":63},[60,356,357],{},"openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -noenc \\\n",[60,359,360],{"class":62,"line":69},[60,361,362],{},"    -keyout my-ca-root.key -out my-ca-root.crt\n",[341,364,366],{"id":365},"signing-with-our-root-ca","Signing With Our Root CA",[16,368,369],{},"Now we use our CA to sign the CSR we created earlier. For this step, it is\nrecommended to use an extensions file to ensure maximum interoperability with\nvarious OpenSSL versions and to correctly specify which domains the certificate\nis valid for.",[16,371,372],{},"The Subject Alternative Name (SAN) extension is critical here. Historically,\nbrowsers matched certificates against the Common Name field, but RFC 2818\n(published in 2000) recommended using SANs instead. Since 2017, major browsers\nhave enforced this, meaning a certificate without a SAN will be rejected\nregardless of what the Common Name says.",[16,374,375,378],{},[57,376,377],{},"DNS.1"," is the variable aspect below. Ensure you set your domain correctly.",[50,380,382],{"className":52,"code":381,"language":54,"meta":55,"style":55},"cat > \"domain.ext\" \u003C\u003CEOF\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, keyEncipherment\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = mydomain.com\nEOF\n",[57,383,384,389,394,399,404,409,414,419],{"__ignoreMap":55},[60,385,386],{"class":62,"line":63},[60,387,388],{},"cat > \"domain.ext\" \u003C\u003CEOF\n",[60,390,391],{"class":62,"line":69},[60,392,393],{},"authorityKeyIdentifier=keyid,issuer\n",[60,395,396],{"class":62,"line":75},[60,397,398],{},"basicConstraints=CA:FALSE\n",[60,400,401],{"class":62,"line":81},[60,402,403],{},"keyUsage = digitalSignature, keyEncipherment\n",[60,405,406],{"class":62,"line":87},[60,407,408],{},"subjectAltName = @alt_names\n",[60,410,411],{"class":62,"line":168},[60,412,413],{},"[alt_names]\n",[60,415,416],{"class":62,"line":174},[60,417,418],{},"DNS.1 = mydomain.com\n",[60,420,421],{"class":62,"line":180},[60,422,423],{},"EOF\n",[50,425,427],{"className":52,"code":426,"language":54,"meta":55,"style":55},"openssl x509 -req -CA my-ca-root.crt -CAkey my-ca-root.key \\\n    -in domain.csr -out ca-signed.crt -days 365 \\\n    -CAcreateserial -extfile domain.ext\n",[57,428,429,434,439],{"__ignoreMap":55},[60,430,431],{"class":62,"line":63},[60,432,433],{},"openssl x509 -req -CA my-ca-root.crt -CAkey my-ca-root.key \\\n",[60,435,436],{"class":62,"line":69},[60,437,438],{},"    -in domain.csr -out ca-signed.crt -days 365 \\\n",[60,440,441],{"class":62,"line":75},[60,442,443],{},"    -CAcreateserial -extfile domain.ext\n",[341,445,447],{"id":446},"verifying-the-certificate","Verifying the Certificate",[16,449,450],{},"Now we can inspect the attributes of our CA-signed certificate using the x509\ncommand group. This time, notice that the issuer and subject are different. The\nissuer reflects our root CA's distinguished name while the subject is the domain\nwe created the CSR for. This distinction is the foundation of certificate\nchains, where trust flows from the root CA down to the end-entity certificate.",[50,452,454],{"className":52,"code":453,"language":54,"meta":55,"style":55},"openssl x509 -text -noout -in ca-signed.crt\n",[57,455,456],{"__ignoreMap":55},[60,457,458],{"class":62,"line":63},[60,459,453],{},[42,461,463],{"id":462},"quick-shortcuts","Quick Shortcuts",[341,465,467],{"id":466},"creating-private-key-and-csr-together","Creating Private Key and CSR Together",[16,469,470],{},"If you want to combine two steps, you can create the private key and CSR in a\nsingle command. This is useful when you do not need the private key for anything\nelse beforehand.",[50,472,474],{"className":52,"code":473,"language":54,"meta":55,"style":55},"openssl req -newkey rsa:2048 -noenc -keyout domain.key -out domain.csr\n",[57,475,476],{"__ignoreMap":55},[60,477,478],{"class":62,"line":63},[60,479,473],{},[42,481,483],{"id":482},"clean-up","Clean Up",[16,485,486,487,490],{},"If you heeded my earlier recommendation of locating temporary test resources in\na disposable directory, simply nuke your ",[57,488,489],{},"\u002Ftmp\u002Fdirectory"," now.",[492,493,494],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":55,"searchDepth":69,"depth":69,"links":496},[497,498,499,500,505,508],{"id":44,"depth":69,"text":45},{"id":114,"depth":69,"text":115},{"id":259,"depth":69,"text":260},{"id":332,"depth":69,"text":333,"children":501},[502,503,504],{"id":343,"depth":75,"text":344},{"id":365,"depth":75,"text":366},{"id":446,"depth":75,"text":447},{"id":462,"depth":69,"text":463,"children":506},[507],{"id":466,"depth":75,"text":467},{"id":482,"depth":69,"text":483},"2023\u002F05\u002F05","Using OpenSSL to generate TLS certificates for your applications to secure your development servers or simply to understand how to create certificates.","md","\u002Fimages\u002Fblog\u002Fgalway-lake.jpg",null,{"updated":515},"2023\u002F05\u002F09","\u002Fblog\u002Fgenerating-tls-certificates-openssl",{"title":11,"description":510},"blog\u002Fgenerating-tls-certificates-openssl",[520,521,522],"tls","pki","security","iHCGdGB5eaTpZVMBuXJE1QPVUwNNlThQuiMNEOssV7s",1778958297099]