[{"data":1,"prerenderedAt":1158},["ShallowReactive",2],{"nav-categories":3,"category-security":8},{"pinned":4,"overflow":7},[5,6],"Technology","Security",[],[9,682],{"id":10,"title":11,"body":12,"category":6,"date":666,"description":667,"extension":668,"image":669,"imageCredit":670,"imageCreditUrl":670,"meta":671,"navigation":189,"path":673,"public":189,"seo":674,"stem":675,"tags":676,"__hash__":681},"posts\u002Fblog\u002Frsa-jwt-nodejs.md","Sign and verify JWT with RSA encryption with NodeJS",{"type":13,"value":14,"toc":654},"minimark",[15,19,22,27,35,42,61,64,68,79,95,100,103,112,121,132,135,146,156,160,166,282,293,302,305,368,468,477,481,484,568,581,585,588,618,629,633,636,639,643,650],[16,17,18],"p",{},"JWT (JSON Web Token) is a compact, self-contained transmission standard for\nsecurely passing claims between parties using JavaScript object notation. It\noperates on the basis of an optional signature (JWS) or optional encryption\n(JWE). This article focuses on JWS, specifically using asymmetric RSA keys with\nthe SHA-256 hashing algorithm to sign and verify tokens.",[16,20,21],{},"A common use case for JWT is stateless client authentication so that servers do\nnot need to rely on stateful sessions. A token is issued once at login, and\nsubsequent requests carry it as proof of identity. Because the token is\ncryptographically signed, the server can verify its authenticity without\nconsulting a database or session store. This only scratches the surface though,\nas JWTs also enable service-to-service interoperability without shared state.",[23,24,26],"h2",{"id":25},"anatomy-of-a-jwt","Anatomy of a JWT",[16,28,29,30,34],{},"A JWT consists of three Base64URL-encoded segments separated by dots:\n",[31,32,33],"code",{},"header.payload.signature",".",[16,36,37,38,41],{},"The header declares the token type and which algorithm was used to produce the\nsignature. For RSA-based signing this will typically be ",[31,39,40],{},"RS256",", meaning RSA\nsignature with SHA-256.",[16,43,44,45,48,49,52,53,56,57,60],{},"The payload carries the claims, which are statements about the entity (usually\nthe user) and any additional metadata. Standard registered claims include ",[31,46,47],{},"iss","\n(issuer), ",[31,50,51],{},"sub"," (subject), ",[31,54,55],{},"aud"," (audience), and ",[31,58,59],{},"exp"," (expiration time as a\nUnix timestamp). You are free to include custom claims alongside these, though\nkeeping payloads lean is good practice since the token travels with every\nrequest.",[16,62,63],{},"The signature is produced by signing the encoded header and payload with the\nprivate key. Anyone holding the corresponding public key can verify the\nsignature without being able to forge a new one. This asymmetry is what makes\nRSA particularly useful for distributed systems where the verifier and the\nissuer are separate services.",[23,65,67],{"id":66},"getting-started","Getting Started",[16,69,70,71,78],{},"We will use the popular\n",[72,73,77],"a",{"href":74,"rel":75},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fjsonwebtoken",[76],"nofollow","jsonwebtoken"," library for signing\nand verification.",[80,81,86],"pre",{"className":82,"code":83,"language":84,"meta":85,"style":85},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npm install jsonwebtoken\n","sh","",[31,87,88],{"__ignoreMap":85},[89,90,93],"span",{"class":91,"line":92},"line",1,[89,94,83],{},[96,97,99],"h3",{"id":98},"generating-the-key-pair","Generating the Key Pair",[16,101,102],{},"First, generate an RSA private key and derive the public key from it. We use\n3072 bits here as a sensible modern default that balances security and\nperformance.",[80,104,106],{"className":82,"code":105,"language":84,"meta":85,"style":85},"openssl genrsa -out private-key.pem 3072\n",[31,107,108],{"__ignoreMap":85},[89,109,110],{"class":91,"line":92},[89,111,105],{},[80,113,115],{"className":82,"code":114,"language":84,"meta":85,"style":85},"openssl rsa -in private-key.pem -pubout -out public-key.pem\n",[31,116,117],{"__ignoreMap":85},[89,118,119],{"class":91,"line":92},[89,120,114],{},[16,122,123,124,127,128,131],{},"A quick note on security hygiene: even with testing keys, you should never\ncommit private keys to version control. A habit I have developed is to store\ninstance-relative files in a directory called ",[31,125,126],{},"instance\u002F"," at the root of the\npackage and ensure that directory is covered by ",[31,129,130],{},".gitignore",". You do not need to\nfollow this exact pattern, but do ensure your private key is excluded from any\nrepository.",[16,133,134],{},"File permissions matter too. Ensure only the file owner can read the key.",[80,136,140],{"className":137,"code":138,"language":139,"meta":85,"style":85},"language-shell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","chmod 600 private-key.pem\n","shell",[31,141,142],{"__ignoreMap":85},[89,143,144],{"class":91,"line":92},[89,145,138],{},[147,148,149],"blockquote",{},[16,150,151,152,155],{},"If using hosting options like EC2, permission requirements may vary due to\nprovider constraints on users. In that context ",[31,153,154],{},"chmod 0400"," (read-only for\nowner) may be more appropriate.",[23,157,159],{"id":158},"signing-a-token","Signing a Token",[16,161,162,163,165],{},"With the keys in place, we can sign a token. The ",[31,164,77],{}," library handles\nheader construction internally based on the options you provide, so you only\nneed to supply the payload, the private key, and the signing options.",[80,167,171],{"className":168,"code":169,"language":170,"meta":85,"style":85},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const fs = require(\"fs\");\nconst jwt = require(\"jsonwebtoken\");\n\nconst privkey = fs.readFileSync(\"instance\u002Fprivate-key.pem\", \"utf-8\");\n\nconst payload = {\n  iss: \"my-auth-service\",\n  sub: \"user-12345\",\n  aud: \"my-application\",\n};\n\nconst options = {\n  algorithm: \"RS256\",\n  expiresIn: \"12h\",\n};\n\nconst token = jwt.sign(payload, privkey, options);\n\nconsole.log(token);\n","javascript",[31,172,173,178,184,191,197,202,208,214,220,226,232,237,243,249,255,260,265,271,276],{"__ignoreMap":85},[89,174,175],{"class":91,"line":92},[89,176,177],{},"const fs = require(\"fs\");\n",[89,179,181],{"class":91,"line":180},2,[89,182,183],{},"const jwt = require(\"jsonwebtoken\");\n",[89,185,187],{"class":91,"line":186},3,[89,188,190],{"emptyLinePlaceholder":189},true,"\n",[89,192,194],{"class":91,"line":193},4,[89,195,196],{},"const privkey = fs.readFileSync(\"instance\u002Fprivate-key.pem\", \"utf-8\");\n",[89,198,200],{"class":91,"line":199},5,[89,201,190],{"emptyLinePlaceholder":189},[89,203,205],{"class":91,"line":204},6,[89,206,207],{},"const payload = {\n",[89,209,211],{"class":91,"line":210},7,[89,212,213],{},"  iss: \"my-auth-service\",\n",[89,215,217],{"class":91,"line":216},8,[89,218,219],{},"  sub: \"user-12345\",\n",[89,221,223],{"class":91,"line":222},9,[89,224,225],{},"  aud: \"my-application\",\n",[89,227,229],{"class":91,"line":228},10,[89,230,231],{},"};\n",[89,233,235],{"class":91,"line":234},11,[89,236,190],{"emptyLinePlaceholder":189},[89,238,240],{"class":91,"line":239},12,[89,241,242],{},"const options = {\n",[89,244,246],{"class":91,"line":245},13,[89,247,248],{},"  algorithm: \"RS256\",\n",[89,250,252],{"class":91,"line":251},14,[89,253,254],{},"  expiresIn: \"12h\",\n",[89,256,258],{"class":91,"line":257},15,[89,259,231],{},[89,261,263],{"class":91,"line":262},16,[89,264,190],{"emptyLinePlaceholder":189},[89,266,268],{"class":91,"line":267},17,[89,269,270],{},"const token = jwt.sign(payload, privkey, options);\n",[89,272,274],{"class":91,"line":273},18,[89,275,190],{"emptyLinePlaceholder":189},[89,277,279],{"class":91,"line":278},19,[89,280,281],{},"console.log(token);\n",[16,283,284,285,288,289,292],{},"The output will be a string beginning with ",[31,286,287],{},"eyJ",", which is the Base64URL\nencoding of ",[31,290,291],{},"{\"alg\"...",". The three dot-separated segments are visible in the raw\ntoken.",[80,294,296],{"className":82,"code":295,"language":84,"meta":85,"style":85},"eyJhbGciOiJSUzI1NiIs***.eyJpc3MiOi***.signature***\n",[31,297,298],{"__ignoreMap":85},[89,299,300],{"class":91,"line":92},[89,301,295],{},[16,303,304],{},"If we decode the first two segments (without verifying) we can see the\nstructure:",[80,306,310],{"className":307,"code":308,"language":309,"meta":85,"style":85},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"alg\": \"RS256\",\n  \"typ\": \"JWT\"\n}\n","json",[31,311,312,318,344,363],{"__ignoreMap":85},[89,313,314],{"class":91,"line":92},[89,315,317],{"class":316},"sMK4o","{\n",[89,319,320,323,327,330,333,336,339,341],{"class":91,"line":180},[89,321,322],{"class":316},"  \"",[89,324,326],{"class":325},"spNyl","alg",[89,328,329],{"class":316},"\"",[89,331,332],{"class":316},":",[89,334,335],{"class":316}," \"",[89,337,40],{"class":338},"sfazB",[89,340,329],{"class":316},[89,342,343],{"class":316},",\n",[89,345,346,348,351,353,355,357,360],{"class":91,"line":186},[89,347,322],{"class":316},[89,349,350],{"class":325},"typ",[89,352,329],{"class":316},[89,354,332],{"class":316},[89,356,335],{"class":316},[89,358,359],{"class":338},"JWT",[89,361,362],{"class":316},"\"\n",[89,364,365],{"class":91,"line":193},[89,366,367],{"class":316},"}\n",[80,369,371],{"className":307,"code":370,"language":309,"meta":85,"style":85},"{\n  \"iss\": \"my-auth-service\",\n  \"sub\": \"user-12345\",\n  \"aud\": \"my-application\",\n  \"iat\": 1674353419,\n  \"exp\": 1674396619\n}\n",[31,372,373,377,396,415,434,451,464],{"__ignoreMap":85},[89,374,375],{"class":91,"line":92},[89,376,317],{"class":316},[89,378,379,381,383,385,387,389,392,394],{"class":91,"line":180},[89,380,322],{"class":316},[89,382,47],{"class":325},[89,384,329],{"class":316},[89,386,332],{"class":316},[89,388,335],{"class":316},[89,390,391],{"class":338},"my-auth-service",[89,393,329],{"class":316},[89,395,343],{"class":316},[89,397,398,400,402,404,406,408,411,413],{"class":91,"line":186},[89,399,322],{"class":316},[89,401,51],{"class":325},[89,403,329],{"class":316},[89,405,332],{"class":316},[89,407,335],{"class":316},[89,409,410],{"class":338},"user-12345",[89,412,329],{"class":316},[89,414,343],{"class":316},[89,416,417,419,421,423,425,427,430,432],{"class":91,"line":193},[89,418,322],{"class":316},[89,420,55],{"class":325},[89,422,329],{"class":316},[89,424,332],{"class":316},[89,426,335],{"class":316},[89,428,429],{"class":338},"my-application",[89,431,329],{"class":316},[89,433,343],{"class":316},[89,435,436,438,441,443,445,449],{"class":91,"line":199},[89,437,322],{"class":316},[89,439,440],{"class":325},"iat",[89,442,329],{"class":316},[89,444,332],{"class":316},[89,446,448],{"class":447},"sbssI"," 1674353419",[89,450,343],{"class":316},[89,452,453,455,457,459,461],{"class":91,"line":204},[89,454,322],{"class":316},[89,456,59],{"class":325},[89,458,329],{"class":316},[89,460,332],{"class":316},[89,462,463],{"class":447}," 1674396619\n",[89,465,466],{"class":91,"line":210},[89,467,367],{"class":316},[16,469,470,471,473,474,476],{},"Notice that ",[31,472,440],{}," (issued at) was added automatically by the library, and ",[31,475,59],{},"\nwas calculated as 12 hours from the issue time. These are both Unix timestamps\nin seconds.",[23,478,480],{"id":479},"verifying-a-token","Verifying a Token",[16,482,483],{},"Verification is where the public key comes in. Any service holding the public\nkey can verify that a token was signed by the corresponding private key without\never needing access to that private key. This separation is the entire point of\nasymmetric cryptography in this context.",[80,485,487],{"className":168,"code":486,"language":170,"meta":85,"style":85},"const fs = require(\"fs\");\nconst jwt = require(\"jsonwebtoken\");\n\nconst pubkey = fs.readFileSync(\"instance\u002Fpublic-key.pem\", \"utf-8\");\n\nconst token = \"eyJhbGciOi...\"; \u002F\u002F token received from client\n\ntry {\n  const decoded = jwt.verify(token, pubkey, {\n    algorithms: [\"RS256\"],\n    issuer: \"my-auth-service\",\n    audience: \"my-application\",\n  });\n  console.log(\"Token is valid:\", decoded);\n} catch (err) {\n  console.error(\"Token verification failed:\", err.message);\n}\n",[31,488,489,493,497,501,506,510,515,519,524,529,534,539,544,549,554,559,564],{"__ignoreMap":85},[89,490,491],{"class":91,"line":92},[89,492,177],{},[89,494,495],{"class":91,"line":180},[89,496,183],{},[89,498,499],{"class":91,"line":186},[89,500,190],{"emptyLinePlaceholder":189},[89,502,503],{"class":91,"line":193},[89,504,505],{},"const pubkey = fs.readFileSync(\"instance\u002Fpublic-key.pem\", \"utf-8\");\n",[89,507,508],{"class":91,"line":199},[89,509,190],{"emptyLinePlaceholder":189},[89,511,512],{"class":91,"line":204},[89,513,514],{},"const token = \"eyJhbGciOi...\"; \u002F\u002F token received from client\n",[89,516,517],{"class":91,"line":210},[89,518,190],{"emptyLinePlaceholder":189},[89,520,521],{"class":91,"line":216},[89,522,523],{},"try {\n",[89,525,526],{"class":91,"line":222},[89,527,528],{},"  const decoded = jwt.verify(token, pubkey, {\n",[89,530,531],{"class":91,"line":228},[89,532,533],{},"    algorithms: [\"RS256\"],\n",[89,535,536],{"class":91,"line":234},[89,537,538],{},"    issuer: \"my-auth-service\",\n",[89,540,541],{"class":91,"line":239},[89,542,543],{},"    audience: \"my-application\",\n",[89,545,546],{"class":91,"line":245},[89,547,548],{},"  });\n",[89,550,551],{"class":91,"line":251},[89,552,553],{},"  console.log(\"Token is valid:\", decoded);\n",[89,555,556],{"class":91,"line":257},[89,557,558],{},"} catch (err) {\n",[89,560,561],{"class":91,"line":262},[89,562,563],{},"  console.error(\"Token verification failed:\", err.message);\n",[89,565,566],{"class":91,"line":267},[89,567,367],{},[16,569,570,571,574,575,577,578,580],{},"The ",[31,572,573],{},"verify"," function does several things simultaneously. It checks that the\nsignature is valid against the public key, that the token has not expired, and\nthat the ",[31,576,47],{}," and ",[31,579,55],{}," claims match what we expect. If any of these checks\nfail, it throws an error rather than returning a decoded payload. Always wrap\nverification in a try\u002Fcatch and treat failures as authentication failures.",[96,582,584],{"id":583},"common-verification-failures","Common Verification Failures",[16,586,587],{},"A few scenarios that will cause verification to throw:",[589,590,591,598,601,611],"ul",{},[592,593,594,595,597],"li",{},"The token has expired (current time is past ",[31,596,59],{},")",[592,599,600],{},"The signature does not match (token was tampered with or signed by a different\nkey)",[592,602,570,603,606,607,610],{},[31,604,605],{},"issuer"," or ",[31,608,609],{},"audience"," options do not match the claims in the token",[592,612,613,614,617],{},"The algorithm in the token header does not match the ",[31,615,616],{},"algorithms"," whitelist",[16,619,620,621,624,625,628],{},"Specifying ",[31,622,623],{},"algorithms: [\"RS256\"]"," is important. Without it, an attacker could\npotentially craft a token with ",[31,626,627],{},"\"alg\": \"none\""," or switch to a symmetric\nalgorithm and trick the verifier into using the public key as an HMAC secret.\nAlways explicitly whitelist the algorithms you expect.",[23,630,632],{"id":631},"putting-it-together","Putting It Together",[16,634,635],{},"In a real application, the signing typically happens in an authentication\nservice at login time, and the verification happens in middleware on every\nsubsequent request. The private key lives only on the auth service, while the\npublic key can be distributed freely to any service that needs to validate\ntokens.",[16,637,638],{},"This pattern scales well because adding a new service that needs to verify\nidentity only requires sharing the public key. No shared secrets, no session\nstores, no database lookups on every request.",[23,640,642],{"id":641},"key-rotation","Key Rotation",[16,644,645,646,649],{},"One consideration worth mentioning is key rotation. Private keys should be\nrotated periodically as a matter of security hygiene. When you rotate, you need\na brief overlap period where both the old and new public keys are accepted for\nverification, since tokens signed with the old key may still be valid (not yet\nexpired). The ",[31,647,648],{},"kid"," (key ID) header parameter exists precisely for this purpose,\nallowing verifiers to look up the correct public key for a given token.",[651,652,653],"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);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":85,"searchDepth":180,"depth":180,"links":655},[656,657,660,661,664,665],{"id":25,"depth":180,"text":26},{"id":66,"depth":180,"text":67,"children":658},[659],{"id":98,"depth":186,"text":99},{"id":158,"depth":180,"text":159},{"id":479,"depth":180,"text":480,"children":662},[663],{"id":583,"depth":186,"text":584},{"id":631,"depth":180,"text":632},{"id":641,"depth":180,"text":642},"2023\u002F06\u002F05","Using RSA asymmetric keys with NodeJS to create a private key signed JWT and verify it with the corresponding public key.","md","\u002Fimages\u002Fblog\u002Fjwt-image.png",null,{"updated":672},"2023\u002F06\u002F08","\u002Fblog\u002Frsa-jwt-nodejs",{"title":11,"description":667},"blog\u002Frsa-jwt-nodejs",[677,678,679,680],"rsa","technology","jwt","encryption","7nA2ft7qBd5Oi4x0xQOl9SrcgQqQFAf5Jtin6WTHPUc",{"id":683,"title":684,"body":685,"category":6,"date":1145,"description":1146,"extension":668,"image":1147,"imageCredit":670,"imageCreditUrl":670,"meta":1148,"navigation":189,"path":1150,"public":189,"seo":1151,"stem":1152,"tags":1153,"__hash__":1157},"posts\u002Fblog\u002Fgenerating-tls-certificates-openssl.md","Generating TLS certificates for server HTTPS",{"type":13,"value":686,"toc":1131},[687,690,699,706,709,713,716,746,752,765,769,772,781,784,789,883,886,895,899,902,927,930,959,968,972,975,978,982,985,1000,1004,1007,1010,1016,1061,1081,1085,1088,1097,1101,1105,1108,1117,1121,1128],[16,688,689],{},"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,691,692,693,698],{},"This exercise walks through the process of creating self-signed TLS certificates\nusing ",[72,694,697],{"href":695,"rel":696},"https:\u002F\u002Fwww.openssl.org",[76],"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,700,701,702,705],{},"The foundational concepts here overlap with asymmetric cryptography applications\nsuch as sign and verify using RSA, as covered in my other post on\n",[72,703,704],{"href":673},"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,707,708],{},"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.",[23,710,712],{"id":711},"creating-a-private-key","Creating a Private Key",[16,714,715],{},"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.",[80,717,719],{"className":137,"code":718,"language":139,"meta":85,"style":85},"# 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",[31,720,721,726,731,736,741],{"__ignoreMap":85},[89,722,723],{"class":91,"line":92},[89,724,725],{},"# With password protection (encrypted key)\n",[89,727,728],{"class":91,"line":180},[89,729,730],{},"openssl genrsa -aes256 -out private.key 2048\n",[89,732,733],{"class":91,"line":186},[89,734,735],{},"# Run one or the other!\n",[89,737,738],{"class":91,"line":193},[89,739,740],{},"# Without password protection (unencrypted key)\n",[89,742,743],{"class":91,"line":199},[89,744,745],{},"openssl genrsa -out private.key 2048\n",[16,747,570,748,751],{},[31,749,750],{},"-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.",[147,753,754],{},[16,755,756,757,760,761,764],{},"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 ",[31,758,759],{},"chmod 600","\nfile permissions even for testing. I also prefer to use\n",[31,762,763],{},"\u002Ftmp\u002Fpath-to-nuke-eventually"," and ensure things are nuked appropriately when\nwrapping up to avoid little leaks and general clutter lying around\ndirectories.",[23,766,768],{"id":767},"creating-a-certificate-signing-request","Creating a Certificate Signing Request",[16,770,771],{},"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.",[80,773,775],{"className":137,"code":774,"language":139,"meta":85,"style":85},"openssl req -key private.key -new -out domain.csr\n",[31,776,777],{"__ignoreMap":85},[89,778,779],{"class":91,"line":92},[89,780,774],{},[16,782,783],{},"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.",[147,785,786],{},[16,787,788],{},"Common Name and FQDN are the primary fields of concern here. The Common Name\nshould match the domain you intend to serve traffic on.",[80,790,792],{"className":137,"code":791,"language":139,"meta":85,"style":85},"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",[31,793,794,799,804,809,814,819,824,829,834,839,844,849,854,859,864,868,873,878],{"__ignoreMap":85},[89,795,796],{"class":91,"line":92},[89,797,798],{},"You are about to be asked to enter information that will be incorporated\n",[89,800,801],{"class":91,"line":180},[89,802,803],{},"into your certificate request.\n",[89,805,806],{"class":91,"line":186},[89,807,808],{},"What you are about to enter is what is called a Distinguished Name or a DN.\n",[89,810,811],{"class":91,"line":193},[89,812,813],{},"There are quite a few fields but you can leave some blank\n",[89,815,816],{"class":91,"line":199},[89,817,818],{},"For some fields there will be a default value,\n",[89,820,821],{"class":91,"line":204},[89,822,823],{},"If you enter '.', the field will be left blank.\n",[89,825,826],{"class":91,"line":210},[89,827,828],{},"-----\n",[89,830,831],{"class":91,"line":216},[89,832,833],{},"Country Name (2 letter code) []:ME\n",[89,835,836],{"class":91,"line":222},[89,837,838],{},"State or Province Name (full name) []:Mordor\n",[89,840,841],{"class":91,"line":228},[89,842,843],{},"Locality Name (eg, city) []:Barad Dur\n",[89,845,846],{"class":91,"line":234},[89,847,848],{},"Organization Name (eg, company) []:Maiar\n",[89,850,851],{"class":91,"line":239},[89,852,853],{},"Organizational Unit Name (eg, section) []:Sauron\n",[89,855,856],{"class":91,"line":245},[89,857,858],{},"Common Name (eg, fully qualified host name) []:mydomain.com\n",[89,860,861],{"class":91,"line":251},[89,862,863],{},"Email Address []:\n",[89,865,866],{"class":91,"line":257},[89,867,190],{"emptyLinePlaceholder":189},[89,869,870],{"class":91,"line":262},[89,871,872],{},"Please enter the following 'extra' attributes\n",[89,874,875],{"class":91,"line":267},[89,876,877],{},"to be sent with your certificate request\n",[89,879,880],{"class":91,"line":273},[89,881,882],{},"A challenge password []:\n",[16,884,885],{},"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.",[80,887,889],{"className":137,"code":888,"language":139,"meta":85,"style":85},"openssl req -text -verify -noout -in domain.csr\n",[31,890,891],{"__ignoreMap":85},[89,892,893],{"class":91,"line":92},[89,894,888],{},[23,896,898],{"id":897},"creating-a-self-signed-certificate","Creating a Self-Signed Certificate",[16,900,901],{},"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.",[80,903,905],{"className":137,"code":904,"language":139,"meta":85,"style":85},"$ 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",[31,906,907,912,917,922],{"__ignoreMap":85},[89,908,909],{"class":91,"line":92},[89,910,911],{},"$ openssl x509 -key private.key -in domain.csr -req -days 30 -out self-signed.crt\n",[89,913,914],{"class":91,"line":180},[89,915,916],{},"Signature ok\n",[89,918,919],{"class":91,"line":186},[89,920,921],{},"subject=\u002FC=ME\u002FST=Mordor\u002FL=Barad Dur\u002FO=Maiar\u002FOU=Sauron\u002FCN=mydomain.com\n",[89,923,924],{"class":91,"line":193},[89,925,926],{},"Getting Private key\n",[16,928,929],{},"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.",[80,931,933],{"className":137,"code":932,"language":139,"meta":85,"style":85},"$ openssl x509 -text -noout -in self-signed.crt\nCertificate:\n    Data:\n        ...\n        ...\n",[31,934,935,940,945,950,955],{"__ignoreMap":85},[89,936,937],{"class":91,"line":92},[89,938,939],{},"$ openssl x509 -text -noout -in self-signed.crt\n",[89,941,942],{"class":91,"line":180},[89,943,944],{},"Certificate:\n",[89,946,947],{"class":91,"line":186},[89,948,949],{},"    Data:\n",[89,951,952],{"class":91,"line":193},[89,953,954],{},"        ...\n",[89,956,957],{"class":91,"line":199},[89,958,954],{},[147,960,961],{},[16,962,963,964,967],{},"If we want to time-bound our certificate we should apply the ",[31,965,966],{},"-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.",[23,969,971],{"id":970},"creating-a-certificate-signed-with-our-certificate-authority","Creating a Certificate Signed With Our Certificate Authority",[16,973,974],{},"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,976,977],{},"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.",[96,979,981],{"id":980},"creating-our-root-ca","Creating Our Root CA",[16,983,984],{},"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.",[80,986,988],{"className":137,"code":987,"language":139,"meta":85,"style":85},"openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -noenc \\\n    -keyout my-ca-root.key -out my-ca-root.crt\n",[31,989,990,995],{"__ignoreMap":85},[89,991,992],{"class":91,"line":92},[89,993,994],{},"openssl req -x509 -sha256 -days 3650 -newkey rsa:2048 -noenc \\\n",[89,996,997],{"class":91,"line":180},[89,998,999],{},"    -keyout my-ca-root.key -out my-ca-root.crt\n",[96,1001,1003],{"id":1002},"signing-with-our-root-ca","Signing With Our Root CA",[16,1005,1006],{},"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,1008,1009],{},"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,1011,1012,1015],{},[31,1013,1014],{},"DNS.1"," is the variable aspect below. Ensure you set your domain correctly.",[80,1017,1019],{"className":137,"code":1018,"language":139,"meta":85,"style":85},"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",[31,1020,1021,1026,1031,1036,1041,1046,1051,1056],{"__ignoreMap":85},[89,1022,1023],{"class":91,"line":92},[89,1024,1025],{},"cat > \"domain.ext\" \u003C\u003CEOF\n",[89,1027,1028],{"class":91,"line":180},[89,1029,1030],{},"authorityKeyIdentifier=keyid,issuer\n",[89,1032,1033],{"class":91,"line":186},[89,1034,1035],{},"basicConstraints=CA:FALSE\n",[89,1037,1038],{"class":91,"line":193},[89,1039,1040],{},"keyUsage = digitalSignature, keyEncipherment\n",[89,1042,1043],{"class":91,"line":199},[89,1044,1045],{},"subjectAltName = @alt_names\n",[89,1047,1048],{"class":91,"line":204},[89,1049,1050],{},"[alt_names]\n",[89,1052,1053],{"class":91,"line":210},[89,1054,1055],{},"DNS.1 = mydomain.com\n",[89,1057,1058],{"class":91,"line":216},[89,1059,1060],{},"EOF\n",[80,1062,1064],{"className":137,"code":1063,"language":139,"meta":85,"style":85},"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",[31,1065,1066,1071,1076],{"__ignoreMap":85},[89,1067,1068],{"class":91,"line":92},[89,1069,1070],{},"openssl x509 -req -CA my-ca-root.crt -CAkey my-ca-root.key \\\n",[89,1072,1073],{"class":91,"line":180},[89,1074,1075],{},"    -in domain.csr -out ca-signed.crt -days 365 \\\n",[89,1077,1078],{"class":91,"line":186},[89,1079,1080],{},"    -CAcreateserial -extfile domain.ext\n",[96,1082,1084],{"id":1083},"verifying-the-certificate","Verifying the Certificate",[16,1086,1087],{},"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.",[80,1089,1091],{"className":137,"code":1090,"language":139,"meta":85,"style":85},"openssl x509 -text -noout -in ca-signed.crt\n",[31,1092,1093],{"__ignoreMap":85},[89,1094,1095],{"class":91,"line":92},[89,1096,1090],{},[23,1098,1100],{"id":1099},"quick-shortcuts","Quick Shortcuts",[96,1102,1104],{"id":1103},"creating-private-key-and-csr-together","Creating Private Key and CSR Together",[16,1106,1107],{},"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.",[80,1109,1111],{"className":137,"code":1110,"language":139,"meta":85,"style":85},"openssl req -newkey rsa:2048 -noenc -keyout domain.key -out domain.csr\n",[31,1112,1113],{"__ignoreMap":85},[89,1114,1115],{"class":91,"line":92},[89,1116,1110],{},[23,1118,1120],{"id":1119},"clean-up","Clean Up",[16,1122,1123,1124,1127],{},"If you heeded my earlier recommendation of locating temporary test resources in\na disposable directory, simply nuke your ",[31,1125,1126],{},"\u002Ftmp\u002Fdirectory"," now.",[651,1129,1130],{},"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":85,"searchDepth":180,"depth":180,"links":1132},[1133,1134,1135,1136,1141,1144],{"id":711,"depth":180,"text":712},{"id":767,"depth":180,"text":768},{"id":897,"depth":180,"text":898},{"id":970,"depth":180,"text":971,"children":1137},[1138,1139,1140],{"id":980,"depth":186,"text":981},{"id":1002,"depth":186,"text":1003},{"id":1083,"depth":186,"text":1084},{"id":1099,"depth":180,"text":1100,"children":1142},[1143],{"id":1103,"depth":186,"text":1104},{"id":1119,"depth":180,"text":1120},"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.","\u002Fimages\u002Fblog\u002Fgalway-lake.jpg",{"updated":1149},"2023\u002F05\u002F09","\u002Fblog\u002Fgenerating-tls-certificates-openssl",{"title":684,"description":1146},"blog\u002Fgenerating-tls-certificates-openssl",[1154,1155,1156],"tls","pki","security","iHCGdGB5eaTpZVMBuXJE1QPVUwNNlThQuiMNEOssV7s",1778958296912]