Skip to content
This repository has been archived by the owner on Feb 17, 2021. It is now read-only.

Commit

Permalink
feat(iot): allow multiple enrollment groups
Browse files Browse the repository at this point in the history
  • Loading branch information
coderbyheart committed Nov 29, 2019
1 parent dc4e330 commit 6c7e408
Show file tree
Hide file tree
Showing 20 changed files with 441 additions and 298 deletions.
8 changes: 8 additions & 0 deletions arm/resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const resourceGroupName = () => 'bifravst'

export const deploymentName = resourceGroupName

/**
* Returns the name of the Device Provisioning Service
*/
export const iotDeviceProvisioningServiceName = () => `${resourceGroupName()}ProvisioningService`
35 changes: 23 additions & 12 deletions cli/bifravst.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import * as program from 'commander'
import chalk from 'chalk'
import * as path from 'path'
import { registerCaCommand } from './commands/register-ca'
import { registerCARootCommand } from './commands/register-ca-root'
import { IotHubClient } from "@azure/arm-iothub";
import { IotDpsClient } from '@azure/arm-deviceprovisioningservices'
import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
import { generateDeviceCommand } from './commands/generate-cert';
import { generateDeviceCommand } from './commands/generate-device-cert';
import { connectCommand } from './commands/connect';
import { run } from './process/run';
import { proofCaPossessionCommand } from './commands/proof-ca-possession';
import { proofCARootPossessionCommand } from './commands/proof-ca-possession';
import { registerCAIntermediateCommand } from './commands/register-ca-intermediate';
import { iotDeviceProvisioningServiceName, resourceGroupName, deploymentName } from '../arm/resources';

const ioTHubDPSConnectionString = ({ deploymentName, resourceGroupName }: { deploymentName: string, resourceGroupName: string }) => async () => (await run({
command: 'az',
Expand Down Expand Up @@ -37,20 +39,33 @@ const getCurrentCreds = () => {
const bifravstCLI = async () => {
const certsDir = path.resolve(process.cwd(), 'certificates')

const resourceGroupName = 'bifravst'
const deploymentName = 'bifravst'
const resourceGroup = resourceGroupName()
const deployment = deploymentName()
const dpsName = iotDeviceProvisioningServiceName()

const getIotHubConnectionString = ioTHubDPSConnectionString({ resourceGroupName, deploymentName })
const getIotHubConnectionString = ioTHubDPSConnectionString({ resourceGroupName: resourceGroup, deploymentName: deployment })
const getIotDpsClient = () => getCurrentCreds().then(creds => new IotDpsClient(creds, creds.tokenInfo.subscription))
const getIotClient = () => getCurrentCreds().then(creds => new IotHubClient(creds, creds.tokenInfo.subscription))

program.description('Bifravst Command Line Interface')

const commands = [
registerCaCommand({
registerCARootCommand({
certsDir,
ioTHubDPSConnectionString: getIotHubConnectionString,
iotDpsClient: getIotDpsClient,
dpsName,
resourceGroup
}),
proofCARootPossessionCommand({
iotDpsClient: getIotDpsClient,
certsDir,
dpsName,
resourceGroup
}),
registerCAIntermediateCommand({
certsDir,
ioTHubDPSConnectionString: getIotHubConnectionString,
iotDpsClient: getIotDpsClient
}),
generateDeviceCommand({
iotClient: getIotClient,
Expand All @@ -60,10 +75,6 @@ const bifravstCLI = async () => {
iotDpsClient: getIotDpsClient,
certsDir
}),
proofCaPossessionCommand({
iotDpsClient: getIotDpsClient,
certsDir
})
]

let ran = false
Expand Down
3 changes: 2 additions & 1 deletion cli/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ export const connectCommand = ({
})
}))

iotHub = registry.iotHub

console.log(chalk.magenta(`Device registration succeeded with IotHub`), chalk.yellow(iotHub))

await fs.writeFile(deviceFiles.registry, JSON.stringify(registry, null, 2), 'utf-8')
iotHub = registry.iotHub
} finally {
const connection = Client.fromConnectionString(`HostName=${iotHub};DeviceId=${deviceId};x509=true`, MqttDevice);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,43 @@ import { ComandDefinition } from './CommandDefinition'
import { randomWords } from '@bifravst/random-words'
import { generateDeviceCertificate } from '../iot/generateDeviceCertificate'
import { IotHubClient } from "@azure/arm-iothub";
import { log, debug } from '../logging'
import { list as listIntermediateCerts } from '../iot/intermediateRegistry'

export const generateDeviceCommand = ({
certsDir,
}: {
iotClient: () => Promise<IotHubClient>,
certsDir: string
}): ComandDefinition => ({
command: 'generate-cert',
command: 'generate-device-cert',
options: [
{
flags: '-d, --deviceId <deviceId>',
description: 'Device ID, if left blank a random ID will be generated',
},
{
flags: '-i, --intermediateCertId <intermediateCertId>',
description: 'ID of the CA intermediate certificate to use, if left blank the first will be used',
},
],
action: async ({ deviceId }: { deviceId: string }) => {
action: async ({ deviceId, intermediateCertId }: { deviceId: string, intermediateCertId: string }) => {
const id = deviceId || (await randomWords({ numWords: 3 })).join('-')

if (!intermediateCertId) {
const intermediateCerts = await listIntermediateCerts({ certsDir })
intermediateCertId = intermediateCerts[0]
}

console.log(chalk.magenta('Intermediate certificate:'), chalk.yellow(intermediateCertId))

await generateDeviceCertificate({
deviceId: id,
certsDir,
log: (...message: any[]) => {
console.log(...message.map(m => chalk.magenta(m)))
},
debug: (...message: any[]) => {
console.log(...message.map(m => chalk.cyan(m)))
},
log,
debug,
intermediateCertId

})
console.log(
chalk.magenta(`Certificate for device ${chalk.yellow(id)} generated.`),
Expand Down
24 changes: 13 additions & 11 deletions cli/commands/proof-ca-possession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,41 @@ import chalk from 'chalk'
import { ComandDefinition } from './CommandDefinition'
import { IotDpsClient } from '@azure/arm-deviceprovisioningservices'
import { promises as fs } from 'fs'
import { caFileLocations } from '../iot/caFileLocations'
import { CARootFileLocations } from '../iot/caFileLocations'

export const proofCaPossessionCommand = ({
export const proofCARootPossessionCommand = ({
certsDir,
iotDpsClient,
resourceGroup,
dpsName,
}: {
certsDir: string
resourceGroup: string
dpsName: string
iotDpsClient: () => Promise<IotDpsClient>
}): ComandDefinition => ({
command: 'proof-ca-possession',
command: 'proof-ca-root-possession',
action: async () => {

const certificateName = 'bifravst-root'
const resourceGroupName = 'bifravst'
const dpsName = 'bifravstProvisioningService'
const certLocations = CARootFileLocations(certsDir)

const armDpsClient = await iotDpsClient()
const certificateName = (await fs.readFile(certLocations.name, 'utf-8')).trim()

const { etag } = await armDpsClient.dpsCertificate.get(certificateName, resourceGroupName, dpsName)
const armDpsClient = await iotDpsClient()

const certLocations = caFileLocations(certsDir)
const { etag } = await armDpsClient.dpsCertificate.get(certificateName, resourceGroup, dpsName)

const verificationCert = await fs.readFile(certLocations.verificationCert, 'utf-8')

console.log(chalk.magenta('Certificate:'), chalk.yellow(certificateName))

await armDpsClient.dpsCertificate.verifyCertificate(certificateName, etag as string, {
certificate: verificationCert,
}, resourceGroupName, dpsName)
}, resourceGroup, dpsName)

console.log(chalk.magenta('Verified root CA certificate.'))
console.log()
console.log(chalk.green('You can now generate device certificates using'), chalk.blueBright('node cli generate-cert'))
console.log(chalk.green('You can now register a CA intermediate certificate using'), chalk.blueBright('node cli register-ca-intermediate'))
},
help: 'Verifies the root CA certificate which is registered with the Device Provisioning System',
})
71 changes: 71 additions & 0 deletions cli/commands/register-ca-intermediate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import chalk from 'chalk'
import { ComandDefinition } from './CommandDefinition'
import { generateCAIntermediate } from '../iot/generateCAIntermediate'
import { ProvisioningServiceClient } from 'azure-iot-provisioning-service'
import { IotDpsClient } from '@azure/arm-deviceprovisioningservices'
import { add as addToIntermediateRegistry } from '../iot/intermediateRegistry'
import { v4 } from 'uuid'
import { log, debug } from '../logging'

export const registerCAIntermediateCommand = ({
certsDir,
ioTHubDPSConnectionString,
}: {
certsDir: string
ioTHubDPSConnectionString: () => Promise<string>
iotDpsClient: () => Promise<IotDpsClient>
}): ComandDefinition => ({
command: 'register-ca-intermediate',
action: async () => {

const id = v4()

const intermediate = await generateCAIntermediate({
id,
certsDir,
log,
debug
})
console.log(chalk.magenta(`CA intermediate certificate generated.`))

await addToIntermediateRegistry({ certsDir, id })

// Create enrollment group

const dpsConnString = await ioTHubDPSConnectionString()

const dpsClient = ProvisioningServiceClient.fromConnectionString(dpsConnString)

const enrollmentGroupId = `bifravst-${id}`

await dpsClient.createOrUpdateEnrollmentGroup({
enrollmentGroupId,
attestation: {
type: 'x509',
//@ts-ignore
x509: {
signingCertificates: {
primary: {
certificate: intermediate.certificate
}
}
}
},
provisioningStatus: "enabled",
reprovisionPolicy: {
migrateDeviceData: true,
updateHubAssignment: true
}
})

console.log(
chalk.magenta(`Created enrollment group for CA intermediate certificiate`),
chalk.yellow(enrollmentGroupId)
)

console.log()

console.log(chalk.green('You can now generate device certificates using'), chalk.blueBright('node cli generate-device-cert'))
},
help: 'Creates a CA intermediate certificate registers it with an IoT Device Provisioning Service enrollment group',
})
81 changes: 81 additions & 0 deletions cli/commands/register-ca-root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import chalk from 'chalk'
import { ComandDefinition } from './CommandDefinition'
import { IotDpsClient } from '@azure/arm-deviceprovisioningservices'
import { generateProofOfPosession } from '../iot/generateProofOfPosession'
import { v4 } from 'uuid'
import { generateCARoot } from '../iot/generateCARoot'
import { log, debug } from '../logging'

export const registerCARootCommand = ({
certsDir,
iotDpsClient,
resourceGroup,
dpsName,
}: {
certsDir: string
resourceGroup: string
dpsName: string
iotDpsClient: () => Promise<IotDpsClient>
}): ComandDefinition => ({
command: 'register-ca-root',
action: async () => {
const certificateName = `bifravst-root-${v4()}`

const root = await generateCARoot({
certsDir,
name: certificateName,
log,
debug
})
console.log(chalk.magenta(`CA root certificate generated.`))

// Register root CA certificate on DPS

const armDpsClient = await iotDpsClient()

await armDpsClient.dpsCertificate.createOrUpdate(
resourceGroup,
dpsName,
certificateName,
{
certificate: root.certificate
},
)

console.log(
chalk.magenta(`CA root registered with DPS.`),
chalk.yellow(dpsName)
)

// Create verification cert

const { etag } = await armDpsClient.dpsCertificate.get(certificateName, resourceGroup, dpsName)
const { properties } = await armDpsClient.dpsCertificate.generateVerificationCode(
certificateName,
etag as string,
resourceGroup,
dpsName
)

if (!properties?.verificationCode) {
throw new Error(`Failed to generate verification code`)
}

await generateProofOfPosession({
certsDir,
log,
debug,
verificationCode: properties.verificationCode
})

console.log(
chalk.magenta(`Generated verification certificate for verification code`),
chalk.yellow(properties.verificationCode)
)

console.log()

console.log(chalk.green('You can now verify the proof of posession using'), chalk.blueBright('node cli proof-ca-root-possession'))
},
help: 'Creates a CA root certificate and registers it with the IoT Device Provisioning Service',
})
Loading

0 comments on commit 6c7e408

Please sign in to comment.