Skip to content

Commit

Permalink
Merge pull request #64 from Nitrokey/aes-encipher
Browse files Browse the repository at this point in the history
Add support for AES encryption/decryption
  • Loading branch information
sosthene-nitrokey committed Oct 17, 2022
2 parents 810b241 + 7e88017 commit ad2d578
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 30 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ SPDX-License-Identifier: CC0-1.0

- Support using authentication keys for decryption and vice-versa with MANAGE SECURITY ENVIRONMENT ([#60][])
- Support PIN resets using a resetting code ([#63][])
- Support AES encryption/decryption ([#64][])

### Bugfixes

- Fix the length of the Digital signature counter DO 0x93 ([#76][])
- PSO:CDS: Increment the signature counter ([#78][])

[#64]: https://github.com/Nitrokey/opcard-rs/pull/64
[#60]: https://github.com/Nitrokey/opcard-rs/pull/60
[#63]: https://github.com/Nitrokey/opcard-rs/pull/63
[#76]: https://github.com/Nitrokey/opcard-rs/pull/76
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ stoppable_thread = "0.2.1"
test-log = "0.2.10"
trussed = { version = "0.1.0", features = ["virt"] }
rand = "0.8.5"
ron = "0.8"
serde_cbor = "0.11"
hex = { version = "0.4", features = ["serde"] }

[features]
std = []
Expand Down
1 change: 1 addition & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl Command {
Self::ComputeDigitalSignature => pso::sign(context.load_state()?),
Self::InternalAuthenticate => pso::internal_authenticate(context.load_state()?),
Self::Decipher => pso::decipher(context.load_state()?),
Self::Encipher => pso::encipher(context.load_state()?),
Self::GenerateAsymmetricKeyPair(mode) => gen_keypair(context.load_state()?, *mode),
Self::TerminateDf => terminate_df(context),
Self::ActivateFile => activate_file(context),
Expand Down
44 changes: 40 additions & 4 deletions src/command/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
use heapless_bytes::Bytes;
use hex_literal::hex;
use iso7816::Status;
use trussed::{
syscall, try_syscall,
types::{KeySerialization, Location, Mechanism},
};

use crate::{
card::{Context, LoadedContext, Options},
Expand Down Expand Up @@ -866,12 +870,44 @@ fn put_cardholder_cert<const R: usize, T: trussed::Client>(
put_arbitrary_do(ctx, to_write)
}

const AES256_KEY_LEN: usize = 32;

fn put_enc_dec_key<const R: usize, T: trussed::Client>(
_ctx: LoadedContext<'_, R, T>,
ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
// TODO: implement
error!("Put data in even mode not yet implemented");
Err(Status::FunctionNotSupported)
if ctx.data.len() != AES256_KEY_LEN {
warn!(
"Attempt at importing an AES of length not {AES256_KEY_LEN}: {}",
ctx.data.len()
);
return Err(Status::ConditionsOfUseNotSatisfied);
}

let new_key = try_syscall!(ctx.backend.client_mut().unsafe_inject_key(
Mechanism::Aes256Cbc,
ctx.data,
Location::Internal,
KeySerialization::Raw,
))
.map_err(|_err| {
error!("Failed to import AES key: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})?
.key;

let old_key = ctx
.state
.internal
.set_aes_key_id(Some(new_key), ctx.backend.client_mut())
.map_err(|_err| {
error!("Failed to set new key: {_err:?}");
Status::UnspecifiedNonpersistentExecutionError
})?;
if let Some(old_key) = old_key {
syscall!(ctx.backend.client_mut().delete(old_key));
}

Ok(())
}

fn put_resetting_code<const R: usize, T: trussed::Client>(
Expand Down
68 changes: 67 additions & 1 deletion src/command/pso.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

use iso7816::Status;

use trussed::try_syscall;
use trussed::types::*;
use trussed::{syscall, try_syscall};

use crate::card::LoadedContext;
use crate::state::KeyRef;
Expand Down Expand Up @@ -214,6 +214,13 @@ pub fn decipher<const R: usize, T: trussed::Client>(
return Err(Status::SecurityStatusNotSatisfied);
}

if ctx.data.is_empty() {
return Err(Status::IncorrectDataParameter);
}
if ctx.data[0] == 0x02 {
return decipher_aes(ctx);
}

let (key_id, mechanism, uif) = decipher_key_mecha_uif(ctx.lend())?;
if uif {
prompt_uif(ctx.lend())?;
Expand Down Expand Up @@ -300,3 +307,62 @@ fn decrypt_ec<const R: usize, T: trussed::Client>(

ctx.reply.expand(&data)
}

fn decipher_aes<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
let key_id = ctx.state.internal.aes_key().ok_or_else(|| {
warn!("Attempt to decipher with AES and no key set");
Status::ConditionsOfUseNotSatisfied
})?;

if (ctx.data.len() - 1) % 16 != 0 {
warn!("Attempt to decipher with AES with length not a multiple of block size");
return Err(Status::IncorrectDataParameter);
}

let plaintext = syscall!(ctx.backend.client_mut().decrypt(
Mechanism::Aes256Cbc,
key_id,
&ctx.data[1..],
&[], // No AAD
&[], // Zero IV
&[] // No authentication tag
))
.plaintext
.ok_or_else(|| {
warn!("Failed decryption");
Status::UnspecifiedCheckingError
})?;
ctx.reply.expand(&plaintext)
}

pub fn encipher<const R: usize, T: trussed::Client>(
mut ctx: LoadedContext<'_, R, T>,
) -> Result<(), Status> {
if !ctx.state.runtime.other_verified {
warn!("Attempt to encipher without PW1 verified");
return Err(Status::SecurityStatusNotSatisfied);
}

let key_id = ctx.state.internal.aes_key().ok_or_else(|| {
warn!("Attempt to decipher with AES and no key set");
Status::ConditionsOfUseNotSatisfied
})?;

if ctx.data.len() % 16 != 0 {
warn!("Attempt to encipher with AES with length not a multiple of block size");
return Err(Status::IncorrectDataParameter);
}

let plaintext = syscall!(ctx.backend.client_mut().encrypt(
Mechanism::Aes256Cbc,
key_id,
ctx.data,
&[],
None
))
.ciphertext;
ctx.reply.expand(&[0x02])?;
ctx.reply.expand(&plaintext)
}
18 changes: 17 additions & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ pub struct Internal {
signing_key: Option<(KeyId, KeyOrigin)>,
confidentiality_key: Option<(KeyId, KeyOrigin)>,
aut_key: Option<(KeyId, KeyOrigin)>,
aes_key: Option<KeyId>,
sign_alg: SignatureAlgorithm,
dec_alg: DecryptionAlgorithm,
aut_alg: AuthenticationAlgorithm,
Expand Down Expand Up @@ -321,10 +322,11 @@ impl Internal {
sign_count: 0,
signing_key: None,
confidentiality_key: None,
aut_key: None,
aes_key: None,
sign_alg: SignatureAlgorithm::default(),
dec_alg: DecryptionAlgorithm::default(),
aut_alg: AuthenticationAlgorithm::default(),
aut_key: None,
fingerprints: Fingerprints::default(),
ca_fingerprints: CaFingerprints::default(),
keygen_dates: KeyGenDates::default(),
Expand Down Expand Up @@ -694,6 +696,20 @@ impl Internal {
Ok(new)
}

pub fn aes_key(&self) -> &Option<KeyId> {
&self.aes_key
}

pub fn set_aes_key_id(
&mut self,
mut new: Option<KeyId>,
client: &mut impl trussed::Client,
) -> Result<Option<KeyId>, Error> {
swap(&mut self.aes_key, &mut new);
self.save(client)?;
Ok(new)
}

pub fn delete_key(
&mut self,
ty: KeyType,
Expand Down
40 changes: 40 additions & 0 deletions tests/command_response.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (C) 2022 Nitrokey GmbH
// SPDX-License-Identifier: LGPL-3.0-only

[
IoTest(
name: "GET CHALLENGE",
cmd_resp: [
IoData(
input: "00 84 0000 0A",
output: And([NonZero, Len(0x0A)])
),
IoData(
input: "00 84 0000 00 0400",
output: And([NonZero, Len(0x0400)])
)
]
),
IoTest(
name: "AES",
cmd_resp: [
// Verify Admin Pin
IoData(input: "00200083 08 3132333435363738"),
// Verify User Pin
IoData(input: "00200082 06 313233343536"),
// Set aes key
IoData(input: "0C DA 00D5 20 FFEEDDCCBBAA00998877665544332211FFEEDDCCBBAA00998877665544332211"),
// Encrypt with AES
IoData(
input: "00 2A 86 80 10 00112233445566778899AABBCCDDEEFF 00",
output: Data("02 d9d2ca17e160427aee649db6912dbfad"),
),
// Decrypt with AES
IoData(
input: "00 2A 80 86 11 02 d9d2ca17e160427aee649db6912dbfad 00",
output: Data("00112233445566778899AABBCCDDEEFF"),
),

]
)
]
Loading

0 comments on commit ad2d578

Please sign in to comment.