1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //! Secretkeeper client acts as the "source" `AuthGraphParticipant` in context of AuthGraph 18 //! Key Exchange, while Secretkeeper itself acts as "sink" `AuthGraphParticipant`. This module 19 //! supports functionality to configure the local "source" `AuthGraphParticipant` for clients. 20 21 extern crate alloc; 22 23 use crate::dice::OwnedDiceArtifactsWithExplicitKey; 24 use authgraph_boringssl::{BoringAes, BoringRng}; 25 use authgraph_core::ag_err; 26 use authgraph_core::error::Error as AgError; 27 use authgraph_core::error::Error; 28 use authgraph_core::key::{ 29 AesKey, CertChain, EcSignKey, EcVerifyKey, Identity, IdentityVerificationDecision, 30 CURVE25519_PRIV_KEY_LEN, IDENTITY_VERSION, 31 }; 32 use authgraph_core::traits; 33 use authgraph_core::traits::AG_KEY_EXCHANGE_PROTOCOL_VERSION_1; 34 use authgraph_core::traits::{AesGcm, Device, EcDsa}; 35 use authgraph_wire::{ErrorCode, SESSION_ID_LEN}; 36 use coset::CborSerializable; 37 use coset::{iana, CoseKey}; 38 use diced_open_dice::derive_cdi_leaf_priv; 39 40 /// Implementation of `authgraph_core::traits::Device` required for configuring the local 41 /// `AuthGraphParticipant` for client. 42 pub struct AgDevice { 43 per_boot_key: AesKey, 44 identity: (EcSignKey, Identity), 45 expected_peer_key: Option<CoseKey>, 46 } 47 48 impl AgDevice { 49 const AG_KE_VERSION: i32 = AG_KEY_EXCHANGE_PROTOCOL_VERSION_1; 50 /// Create a new `AgDevice`, using dice_artifacts for `Identity`. new( dice_artifacts: &OwnedDiceArtifactsWithExplicitKey, expected_peer_key: Option<CoseKey>, ) -> Result<Self, AgError>51 pub fn new( 52 dice_artifacts: &OwnedDiceArtifactsWithExplicitKey, 53 expected_peer_key: Option<CoseKey>, 54 ) -> Result<Self, AgError> { 55 let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts) 56 .map_err(|_| ag_err!(InternalError, "Failed to get private key"))?; 57 let identity = Identity { 58 version: IDENTITY_VERSION, 59 cert_chain: CertChain::from_slice( 60 dice_artifacts 61 .explicit_key_dice_chain() 62 .ok_or(ag_err!(InternalError, "Dice chain missing"))?, 63 )?, 64 policy: None, 65 }; 66 67 let per_boot_key = BoringAes {}.generate_key(&mut BoringRng {})?; 68 69 Ok(Self { 70 per_boot_key, 71 identity: ( 72 EcSignKey::Ed25519( 73 // AuthGraph supports storing only the Ed25519 "seed", which is first 32 bytes 74 // of the private key, instead of the full private key. 75 cdi_leaf_priv.as_array()[0..CURVE25519_PRIV_KEY_LEN].try_into().map_err( 76 |e| { 77 ag_err!( 78 InternalError, 79 "Failed to construct private signing key {:?}", 80 e 81 ) 82 }, 83 )?, 84 ), 85 identity, 86 ), 87 expected_peer_key, 88 }) 89 } 90 } 91 92 impl Device for AgDevice { get_or_create_per_boot_key( &self, _aes: &dyn traits::AesGcm, _rng: &mut dyn traits::Rng, ) -> Result<AesKey, AgError>93 fn get_or_create_per_boot_key( 94 &self, 95 _aes: &dyn traits::AesGcm, 96 _rng: &mut dyn traits::Rng, 97 ) -> Result<AesKey, AgError> { 98 Ok(self.per_boot_key.clone()) 99 } 100 get_per_boot_key(&self) -> Result<AesKey, AgError>101 fn get_per_boot_key(&self) -> Result<AesKey, AgError> { 102 Ok(self.per_boot_key.clone()) 103 } 104 get_identity(&self) -> Result<(Option<EcSignKey>, Identity), AgError>105 fn get_identity(&self) -> Result<(Option<EcSignKey>, Identity), AgError> { 106 let (sign_key, identity) = self.identity.clone(); 107 Ok((Some(sign_key), identity)) 108 } 109 get_cose_sign_algorithm(&self) -> Result<iana::Algorithm, AgError>110 fn get_cose_sign_algorithm(&self) -> Result<iana::Algorithm, AgError> { 111 // Microdroid Manager uses EdDSA as the signing algorithm while doing DICE derivation. 112 Ok(iana::Algorithm::EdDSA) 113 } 114 sign_data(&self, _ecdsa: &dyn traits::EcDsa, _data: &[u8]) -> Result<Vec<u8>, AgError>115 fn sign_data(&self, _ecdsa: &dyn traits::EcDsa, _data: &[u8]) -> Result<Vec<u8>, AgError> { 116 // Since the private signing key is already returned in the `get_identity` method of this 117 // implementation, this method can be marked `Unimplemented`. 118 Err(ag_err!(Unimplemented, "Unexpected signing request when the signing key available")) 119 } 120 evaluate_identity( &self, _latest_identity: &Identity, _previous_identity: &Identity, ) -> Result<IdentityVerificationDecision, AgError>121 fn evaluate_identity( 122 &self, 123 _latest_identity: &Identity, 124 _previous_identity: &Identity, 125 ) -> Result<IdentityVerificationDecision, AgError> { 126 // AuthGraph Key Exchange protocol does not require this. Additionally, Secretkeeper 127 // clients create fresh, short-lived sessions and their identity does not change within 128 // a session. 129 Err(ag_err!(Unimplemented, "Not required")) 130 } 131 get_version(&self) -> i32132 fn get_version(&self) -> i32 { 133 Self::AG_KE_VERSION 134 } 135 record_shared_sessions( &mut self, _peer_identity: &Identity, _session_id: &[u8; SESSION_ID_LEN], _shared_keys: &[Vec<u8>; 2], _sha256: &dyn traits::Sha256, ) -> Result<(), AgError>136 fn record_shared_sessions( 137 &mut self, 138 _peer_identity: &Identity, 139 _session_id: &[u8; SESSION_ID_LEN], 140 _shared_keys: &[Vec<u8>; 2], 141 _sha256: &dyn traits::Sha256, 142 ) -> Result<(), AgError> { 143 // There are alternative ways of recording the shared session such as inspecting the output 144 // of KE methods. 145 Ok(()) 146 } 147 validate_peer_identity( &self, identity: &Identity, ecdsa: &dyn EcDsa, ) -> Result<EcVerifyKey, Error>148 fn validate_peer_identity( 149 &self, 150 identity: &Identity, 151 ecdsa: &dyn EcDsa, 152 ) -> Result<EcVerifyKey, Error> { 153 if identity.cert_chain.dice_cert_chain.is_some() { 154 return Err(ag_err!(InvalidPeerKeKey, "Expected peer's DICE chain to be None")); 155 } 156 let root_key = identity.validate(ecdsa)?; 157 158 if let Some(expected_key) = &self.expected_peer_key { 159 // Do bit by bit comparison of the keys. This assumes the 2 keys are equivalently 160 // canonicalized 161 if root_key.get_key_ref() != expected_key { 162 return Err(ag_err!( 163 InvalidPeerKeKey, 164 "Peer identity did not match the expected identity" 165 )); 166 } 167 } 168 Ok(root_key) 169 } 170 validate_shared_sessions( &self, _peer_identity: &Identity, _session_id: &[u8; SESSION_ID_LEN], _shared_keys: &[Vec<u8>], _sha256: &dyn traits::Sha256, ) -> Result<(), Error>171 fn validate_shared_sessions( 172 &self, 173 _peer_identity: &Identity, 174 _session_id: &[u8; SESSION_ID_LEN], 175 _shared_keys: &[Vec<u8>], 176 _sha256: &dyn traits::Sha256, 177 ) -> Result<(), Error> { 178 // Currently, there is no use of validation of shared session to application protocol. 179 Ok(()) 180 } 181 } 182