Alexander Afanasyev | 7c6aeb0 | 2014-04-10 19:59:19 -0700 | [diff] [blame] | 1 | Security Library Tutorial |
| 2 | ========================= |
| 3 | |
| 4 | Key Management |
| 5 | -------------- |
| 6 | |
| 7 | Identity, Key and Certificates |
| 8 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 9 | |
| 10 | All keys, certificates and their corresponding identities are managed by |
| 11 | KeyChain. |
| 12 | |
| 13 | Before signing a packet, you need to assure that the signing key and its |
| 14 | corresponding identity certificate exist in the KeyChain. The private |
| 15 | part of the signing key is used to generate signature, while the |
| 16 | identity certificate is used to constructed the KeyLocator. |
| 17 | |
| 18 | In KeyChain, keys and certificates are managed in terms of identities |
| 19 | which are expressed by namespaces (e.g., ``/ndn/edu/ucla/irl/yingdi``, or |
| 20 | ``/ndn/edu/ucla/boelter\_hall/room\_4805``). Each pair of keys belongs to |
| 21 | only one identity, and it is named by the identity name appended with a |
| 22 | key ID (e.g., ``/ndn/edu/ucla/irl/yingdi/ksk-1234567890``, or |
| 23 | ``/ndn/edu/ucla/boelter\_hall/room\_4805/ksk-1357924680``). However, one |
| 24 | identity may have more than one pair of keys, but only one of them is |
| 25 | the **default key** of the identity. A key pair without any identity |
| 26 | certificates is not quite useful. A key pair may have more than one |
| 27 | identity certificates, but only one of them is the **default |
| 28 | certificate**. Therefore, for a given identity, there is at only one |
| 29 | default identity certificate, which is the default certificates of its |
| 30 | default key. |
| 31 | |
| 32 | While keys and certificates can be created offline using NDN security |
| 33 | tools **ndnsec**, they can be created online using the KeyChain API. The |
| 34 | simplest way is to call ``KeyChain::createIdentity``. |
| 35 | |
| 36 | .. code-block:: cpp |
| 37 | |
| 38 | KeyChain keyChain; |
| 39 | Name identity("/ndn/test/alice"); |
| 40 | |
| 41 | Name certificateName = keyChain.createIdentity(identity); |
| 42 | |
| 43 | ``KeyChain::createIdentity`` returns the default certificate name of the |
| 44 | supplied identity, and always assures that the supplied identity has a |
| 45 | default key and a default certificate. If the default key of the |
| 46 | identity does not exist, ``createIdentity`` will create one. If the |
| 47 | default certificate of the identity does not exist, ``createIdentity`` |
| 48 | will generate a self-signed certificate of the default key as the |
| 49 | default certificate. |
| 50 | |
| 51 | System Default Identity |
| 52 | ~~~~~~~~~~~~~~~~~~~~~~~ |
| 53 | |
| 54 | There is a default key for a particular identity, and a default identity |
| 55 | certificate for a particular key. And, there is also a **default |
| 56 | identity** for the system, which is the user of the system. The default |
| 57 | identity can be configured using ndnsec tools only. You cannot configure |
| 58 | through the security library API. |
| 59 | |
| 60 | Get and Set Default Keys/Certificates |
| 61 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 62 | |
| 63 | If you know the exact names of keys and certificates, you can call |
| 64 | ``KeyChain::getPublicKey`` and ``KeyChain::getCertificate``. |
| 65 | |
| 66 | .. code-block:: cpp |
| 67 | |
| 68 | KeyChain keyChain; |
| 69 | Name aliceKeyName("/ndn/test/alice/ksk-1394129695025"); |
| 70 | Name aliceCertName("/ndn/test/KEY/alice/ksk-1394129695025/ID-CERT/%FD%01D%98%9A%F2%3F"); |
| 71 | |
| 72 | shared_ptr<PublicKey> aliceKey = keyChain.getPublicKey(aliceKeyName); |
| 73 | shared_ptr<IdentityCertificate> aliceCert = keyChain.getCertificate(aliceCertName); |
| 74 | |
| 75 | It might be difficult to remember the exact name of keys and |
| 76 | certificates, but it might be easier to remember identity names. The |
| 77 | security library provides a list of methods to locate the default key |
| 78 | name and certificate name of an identity. |
| 79 | |
| 80 | .. code-block:: cpp |
| 81 | |
| 82 | KeyChain keyChain; |
| 83 | Name alice("/ndn/test/alice"); |
| 84 | |
| 85 | Name aliceKeyName = keyChain.getDefaultKeyNameForIdentity(alice); |
| 86 | Name aliceCertName = keyChain.getDefaultCertificateNameForKey(aliceKeyName); |
| 87 | |
| 88 | /* following code is equivalent to the two lines above */ |
| 89 | Name aliceCertName2 = keyChain.getDefaultCertificateNameForIdentity(alice); |
| 90 | |
| 91 | You can also manually set default key for an identity and default |
| 92 | certificate for a key through KeyChain. |
| 93 | |
| 94 | .. code-block:: cpp |
| 95 | |
| 96 | KeyChain keyChain; |
| 97 | |
| 98 | Name aliceKeyName("/ndn/test/alice/ksk-1394129695025"); |
| 99 | Name aliceCertName("/ndn/test/KEY/alice/ksk-1394129695025/ID-CERT/%FD%01D%98%9A%F2%3F"); |
| 100 | |
| 101 | keyChain.setDefaultKeyNameForIdentity(aliceKeyName); |
| 102 | keyChain.getDefaultCertificateNameForKey(aliceCertName); |
| 103 | |
| 104 | Create Keys Manually |
| 105 | ~~~~~~~~~~~~~~~~~~~~ |
| 106 | |
| 107 | You can call ``KeyChain::generateRSAKeyPair`` to generate an RSA key |
| 108 | pair. Note that generated key pair is not set as the default key of the |
| 109 | identity, so you need to set it manually by calling |
| 110 | ``KeyChain::setDefaultKeyNameForIdentity``. There is also a helper |
| 111 | method "KeyChain::generateRSAKeyPairAsDefault", which combines the two |
| 112 | steps into one. |
| 113 | |
| 114 | .. code-block:: cpp |
| 115 | |
| 116 | KeyChain keyChain; |
| 117 | Name alice("/ndn/test/alice"); |
| 118 | |
| 119 | Name aliceKeyName = keyChain.generateRSAKeyPair(alice); |
| 120 | keyChain.setDefaultKeyNameForIdentity(aliceKeyName); |
| 121 | |
| 122 | Name aliceKeyName2 = keyChain.generateRSAKeyPairAsDefault(alice); // Now the key with the name aliceKeyName2 becomes alice's default key |
| 123 | |
| 124 | Create Identity Certificate Manually |
| 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 126 | |
| 127 | If you have created a key pair, you can generate a self-signed |
| 128 | certificate for the key by calling ``KeyChain::selfSign``. |
| 129 | |
| 130 | .. code-block:: cpp |
| 131 | |
| 132 | KeyChain keyChain; |
| 133 | Name aliceKeyName("/ndn/test/alice/ksk-1394129695025"); |
| 134 | |
| 135 | shared_ptr<IdentityCertificate> aliceCert = keyChain.selfSign(aliceKeyName); |
| 136 | |
| 137 | You can sign a public key using a different key, but this signing |
| 138 | process may take several steps. Note that this time, the signing key is |
| 139 | vouching for the signed key, so you need to specify more details, such |
| 140 | as the validity, subject descriptions. The first step is to prepare the |
| 141 | unsigned identity certificate by calling |
| 142 | ``KeyChain::prepareUnsignedIdentityCertificate``. And the second step is |
| 143 | to sign the identity certificate. We will talk about the signing methods |
| 144 | in `Packet Signing <#packet_signing>`__. |
| 145 | |
| 146 | .. code-block:: cpp |
| 147 | |
| 148 | KeyChain keyChain; |
| 149 | |
| 150 | Name signingIdentity("/ndn/test"); |
| 151 | Name aliceKeyName("/ndn/test/alice/ksk-1394129695025"); |
| 152 | MillisecondsSince1970 notBefore = getNow(); |
| 153 | MillisecondsSince1970 notAfter = notBefore + 630720000; |
| 154 | vector<CertificateSubjectDescription> subjectDescription; |
| 155 | subjectDescription.push_back(CertificateSubjectDescription("2.5.4.41", "Alice")); // push any subject description into the list. |
| 156 | |
| 157 | shared_ptr<IdentityCertificate> = aliceCert |
| 158 | keyChain.prepareUnsignedIdentityCertificate(aliceKeyName, signingIdentity, notBefore, notAfter, subjectDescription); |
| 159 | |
| 160 | keyChain.signByIdentity(*aliceCert, signingIdentity); |
| 161 | |
| 162 | Packet Signing |
| 163 | -------------- |
| 164 | |
| 165 | When keys and certificates are ready for use, you can sign packet using |
| 166 | them. There are two ways to sign a packet: |
| 167 | |
| 168 | 1. by specifying the name of the identity certificate belonging to the |
| 169 | signing key. |
| 170 | 2. by specifying the identity to which the signing key belongs |
| 171 | |
| 172 | Sign With Certificate Name |
| 173 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 174 | |
| 175 | If we specify the exact certificate name when signing a packet, the |
| 176 | certificate name (without version number) is put into the KeyLocator TLV |
| 177 | in the SignatureInfo. KeyChain will look up the corresponding private |
| 178 | key in the Trusted Platform Module (TPM), and use the private key to |
| 179 | generate the signature. |
| 180 | |
| 181 | .. code-block:: cpp |
| 182 | |
| 183 | KeyChain keyChain; |
| 184 | |
| 185 | Name aliceCertName("/ndn/test/KEY/alice/ksk-1394129695025/ID-CERT/%FD%01D%98%9A%F2%3F"); |
| 186 | Data data("/ndn/test/alice/test-data"); |
| 187 | |
| 188 | keyChain.sign(data, aliceCertName); |
| 189 | |
| 190 | When ``KeyChain::sing`` returns, the SignatureInfo and SignatureValue |
| 191 | TLVs of the supplied data will be set. |
| 192 | |
| 193 | Sign With Identity Name |
| 194 | ~~~~~~~~~~~~~~~~~~~~~~~ |
| 195 | |
| 196 | If we only specify the identity name when signing a packet, the name of |
| 197 | the identity's default certificate will be put into the KeyLocator TLV |
| 198 | in the SingatureInfo, and the identity's default key is used to sign the |
| 199 | packet. Please make sure the default key and certificates of the signing |
| 200 | identity is initialized correctly before signing, otherwise, KeyChain |
| 201 | will create key and self-signed certificate for signing (which is not |
| 202 | quite useful). |
| 203 | |
| 204 | .. code-block:: cpp |
| 205 | |
| 206 | KeyChain keyChain; |
| 207 | |
| 208 | Name alice("/ndn/test/alice"); |
| 209 | Data data("/ndn/test/alice/test-data"); |
| 210 | |
| 211 | keyChain.signByIdentity(data, alice); |
| 212 | |
| 213 | Sign Interest Packet |
| 214 | ~~~~~~~~~~~~~~~~~~~~ |
| 215 | |
| 216 | Signing an interest packet is the same as signing a Data packet. The |
| 217 | only difference is that the SignatureInfo And SignatureValue TLV are |
| 218 | encoded as the last two components of the interest name. |
| 219 | |
| 220 | Packet Validation |
| 221 | ----------------- |
| 222 | |
| 223 | Packet validation is done through a **Validator**. Validator is a |
| 224 | virtual class, two pure virtual methods must be implemented in order to |
| 225 | construct a working validator: |
| 226 | |
| 227 | .. code-block:: cpp |
| 228 | |
| 229 | class Validator { |
| 230 | ... |
| 231 | protected: |
| 232 | virtual void |
| 233 | checkPolicy (const Data& data, |
| 234 | int stepCount, |
| 235 | const OnDataValidated &onValidated, |
| 236 | const OnDataValidationFailed &onValidationFailed, |
| 237 | std::vector<shared_ptr<ValidationRequest> >& nextSteps) = 0; |
| 238 | |
| 239 | virtual void |
| 240 | checkPolicy (const Interest& interest, |
| 241 | int stepCount, |
| 242 | const OnInterestValidated &onValidated, |
| 243 | const OnInterestValidationFailed &onValidationFailed, |
| 244 | std::vector<shared_ptr<ValidationRequest> >& nextSteps) = 0; |
| 245 | ... |
| 246 | |
| 247 | What you need to do inside these two methods is to check whether the |
| 248 | packet and signer comply with your policies, and whether their signature |
| 249 | can be verified. If the packet can be validated, you should call the |
| 250 | ``onValidated`` callback function to trigger packet processing, |
| 251 | otherwise the ``onValidationFailed`` callback should be invoked. If you |
| 252 | need more information (e.g., other certificates), you can construct |
| 253 | several ``ValidationRequest`` and push them into nextSteps. |
| 254 | |
| 255 | .. code-block:: cpp |
| 256 | |
| 257 | class ValidationRequest { |
| 258 | public: |
| 259 | Interest m_interest; // An interest packet to fetch the requested data. |
| 260 | OnDataValidated m_onValidated; // A callback function if the requested certificate is validated. |
| 261 | OnDataValidationFailed m_onDataValidated; // A callback function if the requested certificate validation fails. |
| 262 | int m_retry; // The number of retrials when there is an interest timeout. |
| 263 | int m_stepCount; // The count of validation steps. |
| 264 | }; |
| 265 | |
| 266 | Security library also provides an ``Validator``, ``ValidatorRegex`` |
| 267 | which has already implemented the two methods (basically for Data policy |
| 268 | checking, the Interest policy checking method always calls |
| 269 | ``onValidationFailed``). |
| 270 | |
| 271 | .. code-block:: cpp |
| 272 | |
| 273 | class ValidatorRegex : public Validator |
| 274 | { |
| 275 | public: |
| 276 | ... |
| 277 | |
| 278 | void |
| 279 | addDataVerificationRule(shared_ptr<SecRuleRelative> rule); |
| 280 | |
| 281 | void |
| 282 | addTrustAnchor(shared_ptr<IdentityCertificate> certificate); |
| 283 | |
| 284 | ... |
| 285 | }; |
| 286 | |
| 287 | With ``ValidatorRegex``, you can specify the validation rules in terms |
| 288 | of [[Regex\|NDN Regular Expression]] via |
| 289 | ``ValidatorRegex::addDataVerificationRule``, and set trust anchor via |
| 290 | ``ValidatorRegex::addTrustAnchor``. |
| 291 | |
| 292 | How to specify regex-based validation rule |
| 293 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 294 | |
| 295 | To specify a ``SecRuleRelative``, you needs to specify two NDN regular |
| 296 | expressions: one for data name matching, and the other for KeyLocator |
| 297 | matching. For each regex, you also need to specify the back reference |
| 298 | pattern to extract parts of the name. Moreover, you need to specify the |
| 299 | relation between two extracted patterns. For example, a typical |
| 300 | hierarchical rule can be written as |
| 301 | |
| 302 | .. code-block:: cpp |
| 303 | |
| 304 | SecRuleRelative rule("^(<>*)$", "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$", |
| 305 | ">", "\\1", "\\1\\2", true); |
| 306 | |
| 307 | This rule indicates that the data name must be under the signer's |
| 308 | namespace. |
| 309 | |
| 310 | How to use validator |
| 311 | ~~~~~~~~~~~~~~~~~~~~ |
| 312 | |
| 313 | Here is an example of how to use the validator |
| 314 | |
| 315 | .. code-block:: cpp |
| 316 | |
| 317 | class Example { |
| 318 | public: |
| 319 | Example(ndn::shared_ptr<ndn::Face> face>) |
| 320 | : m_face(face) |
| 321 | { |
| 322 | ndn::shared_ptr<ndn::ValidatorRegex> validator(new ndn::ValidatorRegex(m_face)); |
| 323 | validator->addDataVerificationRule(ndn::make_shared<ndn::SecRuleRelative>("^(<>*)$", |
| 324 | "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$", |
| 325 | ">", "\\1", "\\1\\2", true)); |
| 326 | ndn::shared_ptr<ndn::IdentityCertificate> anchor = ndn::io::load<IdentityCertificate>("ndn-root.cert"); |
| 327 | validator->addTrustAnchor(anchor); |
| 328 | m_validator = validator; |
| 329 | } |
| 330 | |
| 331 | virtual |
| 332 | ~Example() {} |
| 333 | |
| 334 | void |
| 335 | sendInterest() |
| 336 | { |
| 337 | Interest interest ("/ndn/test/data"); |
| 338 | m_face->expressInterest(interest, |
| 339 | bind(&Example::onData, this, _1, _2), |
| 340 | bind(&Example::onTimeout, this, _1)); |
| 341 | } |
| 342 | |
| 343 | void |
| 344 | onData(const ndn::Interest& interest, const ndn::Data& data) |
| 345 | { |
| 346 | m_validator->validate(data, |
| 347 | ndn::bind(&Example::onValidated, this, _1), |
| 348 | ndn::bind(&Example::onValidationFailed, this, _1, _2)); |
| 349 | } |
| 350 | |
| 351 | void onTimeout(const ndn::Interest& interest) {} |
| 352 | |
| 353 | void onValidated(const ndn::shared_ptr<const ndn::Data>& data) {} |
| 354 | |
| 355 | void onValidationFailed(const ndn::shared_ptr<const ndn::Data>& data, const std::string& failInfo) {} |
| 356 | |
| 357 | private: |
| 358 | ndn::shared_ptr<ndn::Face> m_face; |
| 359 | ndn::shared_ptr<ndn::Validator> m_validator; |
| 360 | }; |