Skip to content

Commit

Permalink
feat(auth): add support for Common Access Cards (#4076)
Browse files Browse the repository at this point in the history
This includes two new scripts to aid in some common testing operations: checking a card PIN and getting a CAC certificate.
  • Loading branch information
eventualbuddha authored Oct 13, 2023
1 parent 7022baa commit f8e32c3
Show file tree
Hide file tree
Showing 22 changed files with 1,045 additions and 14 deletions.
25 changes: 25 additions & 0 deletions libs/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ role, and election hash.
./scripts/read-java-card-details
```

### Check PIN Script

This script prompts you for a PIN and asks the Java Card to verify it.

```
# check a VxSuite card's PIN
VX_MACHINE_TYPE=admin ./scripts/check-pin --vxsuite
# check a Common Access Card (CAC) PIN
./scripts/check-pin --cac
```

### Production Machine Cert Signing Request Creation Script

This script creates a production machine cert signing request, using the
Expand Down Expand Up @@ -154,3 +166,16 @@ The following command generates keys and certs for tests:
```
./scripts/generate-test-keys-and-certs
```

### Get Common Access Card (CAC) Certificate Script

This script gets a CAC certificate from a CAC card. It's meant to be used for
development and testing.

```
./scripts/get-cac-cert
```

Prints the certificate in PEM format to stdout by default. Use the `--output`
option to write the certificate to a file, or the `--json` option to print the
certificate metadata in JSON format.
1 change: 1 addition & 0 deletions libs/auth/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
...shared,
coveragePathIgnorePatterns: [
'src/index.ts',
'src/cac/index.ts',
'src/intermediate-scripts',
'src/jurisdictions.ts',
'src/test_utils.ts',
Expand Down
4 changes: 4 additions & 0 deletions libs/auth/scripts/cac-get-cert
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

require('esbuild-runner/register');
require('./cac_get_cert').main(process.argv.slice(2));
89 changes: 89 additions & 0 deletions libs/auth/scripts/cac_get_cert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createWriteStream } from 'fs';
import { throwIllegalValue } from '@votingworks/basics';
import { CARD_DOD_CERT, CommonAccessCard } from '../src/cac';
import { waitForReadyCardStatus } from './utils';

/**
* Gets the certificate from a Common Access Card.
*/
export async function main(args: readonly string[]): Promise<void> {
let out: NodeJS.WritableStream = process.stdout;
let format: 'pem' | 'json' = 'pem';

for (let i = 0; i < args.length; i += 1) {
const arg = args[i];

switch (arg) {
case '-o':
case '--output': {
i += 1;
const outputFilePath = args[i];
if (!outputFilePath) {
process.stderr.write(
`error: cac-get-cert: missing argument for ${arg}\n`
);
process.exit(1);
}
out = createWriteStream(outputFilePath);
break;
}

case '--json': {
format = 'json';
break;
}

case '--pem': {
format = 'pem';
break;
}

case '-h':
case '--help': {
process.stdout.write(
`Usage: ${process.argv[1]} [-o OUTPUT_FILE] [--pem (default)|--json]\n`
);
process.exit(0);
break;
}

default: {
process.stderr.write(`error: cac-get-cert: unknown argument: ${arg}\n`);
process.exit(1);
}
}
}

const card = new CommonAccessCard();
await waitForReadyCardStatus(card);

switch (format) {
case 'pem': {
const pem = await card.getCertificate({
objectId: CARD_DOD_CERT.OBJECT_ID,
});
out.write(pem.toString('utf-8'), () => {
process.exit(0);
});
break;
}

case 'json': {
const cardStatus = await card.getCardStatus();

if (cardStatus.status !== 'ready') {
process.stderr.write('error: card not ready\n');
process.exit(1);
}

out.end(JSON.stringify(cardStatus.cardDetails, undefined, 2), () => {
process.exit(0);
});
break;
}

default: {
throwIllegalValue(format);
}
}
}
4 changes: 4 additions & 0 deletions libs/auth/scripts/check-pin
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

require('esbuild-runner/register');
require('./check_pin').main(process.argv.slice(2));
58 changes: 58 additions & 0 deletions libs/auth/scripts/check_pin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createInterface } from 'readline';
import { JavaCard } from '../src';
import { CommonAccessCardDetails, CommonAccessCard } from '../src/cac';
import { CardDetails, PinProtectedCard, StatefulCard } from '../src/card';
import { waitForReadyCardStatus } from './utils';

/**
* Checks whether a PIN is correct.
*/
export async function main(args: readonly string[]): Promise<void> {
let card: PinProtectedCard &
StatefulCard<CardDetails | CommonAccessCardDetails>;

for (const arg of args) {
switch (arg) {
case '--vxsuite': {
// default
break;
}

case '--cac': {
card = new CommonAccessCard();
break;
}

case '--help':
case '-h': {
process.stdout.write(`Usage: check-pin [--vxsuite (default)|--cac]\n`);
process.exit(0);
break;
}

default: {
process.stderr.write(`Unknown argument: ${arg}\n`);
process.exit(1);
}
}
}

const pin = await new Promise<string>((resolve) => {
createInterface(process.stdin, process.stdout).question(
'Enter PIN: ',
resolve
);
});

card ??= new JavaCard();
console.time('waitForReadyCardStatus');
await waitForReadyCardStatus(card);
console.timeEnd('waitForReadyCardStatus');

console.time('checkPin');
const result = await card.checkPin(pin);
console.timeEnd('checkPin');

console.log(result);
process.exit(result.response === 'correct' ? 0 : 1);
}
8 changes: 4 additions & 4 deletions libs/auth/scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { sleep } from '@votingworks/basics';

import { Card, CardStatusReady } from '../src/card';
import { CardStatusReady, StatefulCard } from '../src/card';

/**
* Waits for a card to have a ready status
*/
export async function waitForReadyCardStatus(
card: Card,
export async function waitForReadyCardStatus<T>(
card: StatefulCard<T>,
waitTimeSeconds = 3
): Promise<CardStatusReady> {
): Promise<CardStatusReady<T>> {
let cardStatus = await card.getCardStatus();
let remainingWaitTimeSeconds = waitTimeSeconds;
while (cardStatus.status !== 'ready' && remainingWaitTimeSeconds > 0) {
Expand Down
1 change: 1 addition & 0 deletions libs/auth/src/apdu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const STATUS_WORD = {
SUCCESS_MORE_DATA_AVAILABLE: { SW1: 0x61 },
FILE_NOT_FOUND: { SW1: 0x6a, SW2: 0x82 },
VERIFY_FAIL: { SW1: 0x63 },
SECURITY_CONDITION_NOT_SATISFIED: { SW1: 0x69, SW2: 0x82 },
} as const;

/**
Expand Down
11 changes: 11 additions & 0 deletions libs/auth/src/cac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# DoD Common Access Card Support

The DoD Common Access Card (CAC) is a smart card issued by the Department of
Defense (DoD) to civilian employees, military personnel, and contractors. The
CAC is used as a general identification card as well as for authentication to
enable access to DoD computer systems and networks.

At the moment, `vxsuite` does not use the CAC for authentication. However, the
`rave` repository which is based on `vxsuite` does. Because making changes in
the `auth` library to support CAC results in a lot of merge conflicts when
rebasing `rave`, the CAC support has pushed upstream to this repository.
29 changes: 29 additions & 0 deletions libs/auth/src/cac/cac-dev-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE8jCCA9qgAwIBAgICCbcwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMx
GDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDEMMAoGA1UECxMDRG9EMQwwCgYDVQQL
EwNQS0kxHTAbBgNVBAMTFERPRCBKSVRDIEVNQUlMIENBLTYzMB4XDTIzMDMwMjAw
MDAwMFoXDTI2MDMwMTIzNTk1OVowfTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1Uu
Uy4gR292ZXJubWVudDEMMAoGA1UECxMDRG9EMQwwCgYDVQQLEwNQS0kxDTALBgNV
BAsTBFVTQUYxKTAnBgNVBAMTIEFJRUxMTy5NSUNIQUVMLkFORFJFVy4xNDA0OTIx
Mjg5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuA7zm0eYH8fEmInf
4FS4FvYJmQeeQOTxQRO3J7e9M7L5PBFvY5pL5/E4dwoj9YBh6BT/FwMRgPhmbr9q
ySQ1eb5dOcxXUrN4tIfKfVSDXkOPtGdqi3mQzBAecX9lEjL/5hAs8FGv0iLgyS5g
MjjnJxBBAgKgALNlfSDcCAho5ppjAMAnBw4tSiVw/X30AOVNhWFs+lf2E3O+b0kD
ITWruOjYB1bEltW/blmfLRz/a4vcQTlRiQZ/OakeYkzxgomeq001fYvVNhm4Kosc
1mRclKgDgVD131xue/HvENXDyygT/p4tjwynOYJMws0p2RCj10UmggID5I3oTYFi
W5/hoQIDAQABo4IBlTCCAZEwHwYDVR0jBBgwFoAUelWI79wGAVbXrwV+Dn69eBHD
7lQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC5uaXQuZGlzYS5taWwvY3Js
L0RPREpJVENFTUFJTENBXzYzLmNybDAOBgNVHQ8BAf8EBAMCBsAwFgYDVR0gBA8w
DTALBglghkgBZQIBCyowHQYDVR0OBBYEFP0cH8uIHxeeHWXOT7DQm1TdVaNhMH4G
CCsGAQUFBwEBBHIwcDA+BggrBgEFBQcwAoYyaHR0cDovL2NybC5uaXQuZGlzYS5t
aWwvc2lnbi9ET0RKSVRDRU1BSUxDQV82My5jZXIwLgYIKwYBBQUHMAGGImh0dHA6
Ly9vY3NwLm5zbjAucmN2cy5uaXQuZGlzYS5taWwwJQYDVR0RBB4wHIEabWljaGFl
bC5haWVsbG8uMkB1cy5hZi5taWwwGwYDVR0JBBQwEjAQBggrBgEFBQcJBDEEEwJV
UzAfBgNVHSUEGDAWBggrBgEFBQcDBAYKKwYBBAGCNwoDDDANBgkqhkiG9w0BAQsF
AAOCAQEAIhcylGXkPyX0pEWaisa+J1Bm3F7Rhtsq3mKdGsVDblMhTg9VVh9Xivyk
6d5OlAIuq4QaISj4CsETtAgiI+FKo2nn0d+4r9SutaUVFT2y607EIO0bscfUxa0G
O2JI475+6EXvsRA863YeMKShToZRII/vhxiVHLxr1gK+uFxe1hxM+cI2XPhWo4AI
dftzKiEV1gmc+P+tsz7vUE3WKuVuV1HAQ8qiQTCDDO8ghhcFjKltwUv+2bBxuGCQ
VDO7kGlhpjYw/PXdeaAAin17cagItbhHt3G9/v6vemZ021iFyBlsmApo0Cy4pY3a
IMZdMJUe17xDlt5XMaCZIALhjrJxmw==
-----END CERTIFICATE-----
Loading

0 comments on commit f8e32c3

Please sign in to comment.