Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
fix: use openssl directly
Browse files Browse the repository at this point in the history
PEM does not support OpenSSL v3
See Dexus/pem#316
  • Loading branch information
coderbyheart committed Sep 22, 2022
1 parent a866d59 commit 4a845c1
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 430 deletions.
16 changes: 8 additions & 8 deletions feature-runner/steps/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const deviceStepRunners = ({
certs[deviceId] ??
(await selfSignedCertificate({ commonName: deviceId }))

progress(`IoT`, `Registering certificate for ${deviceId}`)
progress(`Registering certificate for ${deviceId}`)
const certRegistrationRes = await iotHub.certificates.createOrUpdate(
iotHubResourceGroup,
iotHubName,
Expand All @@ -90,7 +90,7 @@ export const deviceStepRunners = ({
verified: true,
} as any,
)
progress(`IoT`, JSON.stringify(certRegistrationRes))
progress(JSON.stringify(certRegistrationRes))
certificates.push(certRegistrationRes)

const verificationCodeRes =
Expand All @@ -103,14 +103,14 @@ export const deviceStepRunners = ({

const verificationCode =
verificationCodeRes.properties?.verificationCode ?? ''
progress(`IoT`, `Verification code: ${verificationCode}`)
progress(`Verification code: ${verificationCode}`)

const verCert = await verificationCert({
commonName: verificationCode,
privateKey: certs[deviceId].key,
})

progress(`IoT`, `Verifying certificate for ${deviceId}`)
progress(`Verifying certificate for ${deviceId}`)
const verifyRes = await iotHub.certificates.verify(
iotHubResourceGroup,
iotHubName,
Expand All @@ -123,9 +123,9 @@ export const deviceStepRunners = ({
].join('\n'),
},
)
progress(`IoT`, JSON.stringify(verifyRes))
progress(JSON.stringify(verifyRes))

progress(`IoT`, `Registering device for ${deviceId}`)
progress(`Registering device for ${deviceId}`)
const deviceCreationResult = await new Promise((resolve, reject) =>
registry.create(
{
Expand All @@ -137,7 +137,7 @@ export const deviceStepRunners = ({
},
),
)
progress(`IoT`, JSON.stringify(deviceCreationResult))
progress(JSON.stringify(deviceCreationResult))

progress(
`Connecting`,
Expand Down Expand Up @@ -266,7 +266,7 @@ export const deviceStepRunners = ({

connection.on('message', async (t: string, message: Buffer) => {
if (!matchDeviceBoundTopic(topic, t)) return
progress(`Iot`, JSON.stringify(message))
progress(JSON.stringify(message))
const m = isRaw
? message.toString('hex')
: JSON.parse(message.toString('utf-8'))
Expand Down
189 changes: 112 additions & 77 deletions feature-runner/steps/device/selfSignedIotCertificate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import {
createCertificate,
createCSR,
createPrivateKey,
getFingerprint,
} from 'pem'
import { execFile } from 'node:child_process'
import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'

const openssl = async (...args: string[]): Promise<string> =>
new Promise((resolve, reject) => {
execFile(
'openssl',
args,
{
timeout: 60 * 1000,
},
(err, stdout, stderr) => {
if (err !== null) {
console.error(`Failed`, 'openssl', ...args)
return reject(stderr)
}
return resolve(stdout)
},
)
})

const createKey = async (out: string) =>
openssl(
'genpkey',
'-out',
out,
'-algorithm',
'RSA',
'-pkeyopt',
'rsa_keygen_bits:2048',
)

const fingerprint = async (certificate: string) => {
return (await openssl('x509', '-in', certificate, '-noout', '-fingerprint'))
.trim()
.split('=')[1]
.replace(/:/g, '')
}

/**
* Create a self-signed certificate for use with Azure IoT
Expand All @@ -19,93 +53,94 @@ export const selfSignedCertificate = async ({
key: string
fingerprint: string
}> => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'memfault-certs-'))
const version = await openssl('version')
if (!version.includes('OpenSSL 3')) {
throw new Error(`Expected OpenSSL version 3.x, got ${version}!`)
}

// Step 1 - Create a key for the first certificate
// openssl genpkey -out device1.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048
const { key } = await new Promise<{ key: string }>((resolve, reject) =>
createPrivateKey(2048, (error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
}),
)
const key = path.join(tempDir, 'certificate.key')
await createKey(key)

// Step 2 - Create a CSR for the first certificate
// openssl req -new -key device1.key -out device1.csr
const { csr } = await new Promise<{ csr: string }>((resolve, reject) =>
createCSR(
{
commonName,
},
(error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
},
),
const csr = path.join(tempDir, 'certificate.csr')
await openssl(
'req',
'-new',
'-key',
key,
'-out',
csr,
'-subj',
`/CN=${commonName}`,
)

// Step 4 - Self-sign certificate 1
// openssl x509 -req -days 365 -in device1.csr -signkey device1.key -out device1.crt
const { certificate } = await new Promise<{ certificate: string }>(
(resolve, reject) =>
createCertificate(
{
days: 2,
csr,
serviceKey: key,
commonName,
},
(error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
},
),
)
// Step 3 - Check the CSR
await openssl('req', '-text', '-in', csr, '-noout')

const { fingerprint } = await new Promise<{ fingerprint: string }>(
(resolve, reject) =>
getFingerprint(certificate, (error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
}),
// Step 4 - Self-sign certificate 1
const certificate = path.join(tempDir, 'certificate.crt')
await openssl(
'x509',
'-req',
'-days',
'2',
'-in',
csr,
'-signkey',
key,
'-out',
certificate,
)

return {
certificate,
key,
fingerprint,
certificate: await readFile(certificate, 'utf-8'),
key: await readFile(key, 'utf-8'),
fingerprint: await fingerprint(certificate),
}
}

export const verificationCert = async ({
commonName,
commonName: verificationCode,
privateKey,
}: {
commonName: string
privateKey: string
}): Promise<{ certificate: string }> =>
new Promise<{ csr: string }>((resolve, reject) =>
createCSR(
{
commonName,
},
(error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
},
),
).then(
async ({ csr }) =>
new Promise((resolve, reject) =>
createCertificate(
{
days: 2,
serviceKey: privateKey,
commonName,
csr,
},
(error, result) => {
if (error !== undefined && error !== null) return reject(error)
resolve(result)
},
),
),
}): Promise<{ certificate: string }> => {
const tempDir = await mkdtemp(
path.join(os.tmpdir(), 'memfault-verification-certs-'),
)
const privateKeyFile = path.join(tempDir, 'certificate.key')
await writeFile(privateKeyFile, privateKey, 'utf-8')
const csr = path.join(tempDir, 'certificate.csr')
await openssl(
'req',
'-new',
'-key',
privateKeyFile,
'-out',
csr,
'-subj',
`/CN=${verificationCode}`,
)

const certificate = path.join(tempDir, 'certificate.csr')
await openssl(
'x509',
'-req',
'-days',
'2',
'-in',
csr,
'-signkey',
privateKeyFile,
'-out',
certificate,
)

return {
certificate: await readFile(certificate, 'utf-8'),
}
}
16 changes: 0 additions & 16 deletions memfault-consumer-group.bicep

This file was deleted.

Loading

0 comments on commit 4a845c1

Please sign in to comment.