Re: ISSUE-12 / ACTION-83: Operation vs. Algorithm parameters

[Moving over to public-webcrypto, since I think that's where this should be.]

On Jun 7, 2013, at 2:53 PM, Ryan Sleevi <sleevi@google.com> wrote:

> On Fri, Jun 7, 2013 at 8:01 AM, Richard Barnes <rbarnes@bbn.com> wrote:
>> Dear WebCrypto group,
>> 
>> Vijay and I took an action at the F2F to look into resolving the current ambiguity around algorithm identifiers and parameters.
>> <http://www.w3.org/2012/webcrypto/track/actions/83>
>> <http://www.w3.org/2012/webcrypto/track/issues/12>
>> Some analysis and a proposal are below.  Comments welcome!
>> 
>> Thanks,
>> --Richard
>> 
>> 
>> PROBLEM STATEMENT
>> =================
>> 
>> Currently, the AlgorithmIdentifier structure appears in three places in the API, with slightly different flavors and semantics.
>> 1. As an input to key creation (generate / import / derive)
>> 2. As an output of key creation (as key.algorithm)
>> 3. As an input to crypto operations
>> 
>> That means that the API implementation has two difficult jobs.  First, it has to translate type (1) identifiers to type (2) identifiers, e.g., by removing generation parameters like "modulusLength".
> 
> Can you clarify how you arrived at this interpretation? The spec
> doesn't say one way or the other, so I'm curious the thinking.

When you sit down to write generateKey/importKey, you need to figure out how to populate the fields in the Key interface based on the arguments listed in the spec.  That raises the question of what value to assign to "key.algorithm".  The only plausible choice is from the "algorithm" parameter, but that's got things like modulusLength that you don't necessarily want to keep around, because they don't really match the semantic of "key.algorithm" (namely "What can this key do?").

(I tried to find the right citation in the spec, but generateKey/importKey are underspecified right now.)


>> Second, it has to compare type (2) and type (3) identifiers, e.g., ignoring operation parameters like "iv".
> 
> Can you clarify when or why?

The only point to having key scoping with key.algorithm is if it's enforced by the API.  So when an algorithm is invoked, the implementation of, say, encrypt() needs to compare key.algorithm with the algorithm parameter provided in the method call.  

(Same deal with the spec here.  Would be good to require this comparison explicitly.)



>> 
>> There aren't any algorithms defined for these transformations and comparisons, so the result is developer confusion.  (I've actually gotten more than one phone call.)
>> 
>> 
>> ANALYSIS
>> ========
>> 
>> It seems like there are three types of parameters running around here:
>> 
>> * Algorithm Parameters  - Things you want to be fixed for the life of a key
>> * Operation Parameters  - Things that may vary over the lifetime of a key
>> * Generation parameters - Things that cannot be changed without making a new key
>> 
>> A first pass at a taxonomy of parameters is in a Google Spreadsheet here:
>> <https://docs.google.com/a/ipv.sx/spreadsheet/ccc?key=0AvGS_cx3xHzXdHhhZExqOEg2NktJai1Ccnc2RUhicEE&usp=sharing>
>> 
>> The algorithm parameters really identify the algorithm itself -- the thing that you want to check when you're doing an operation.  Conversely, the operation parameters are the things you want to ignore in that check.  Likewise, the generation parameters are the things that you don't need to keep in the algorithm identifier after key generation.
>> 
>> It's worth noting that these concepts are important mainly for encryption, signing, and MAC.  They don't matter much for digest or KDF algorithms, so we might handle those differently.
>> 
> 
> Richard,
> 
> Thanks for attempting to take the time to do this. However, it seems
> that you didn't take into consideration the concerns I raised during
> the face to face and the previous calls that explained the design
> rationale behind this.

Looking back at the F2F minutes, it looks to me like your two major concerns are (1) having a clear taxonomy, and (2) compatibility with legacy APIs (e.g., PKCS11).  I've tried to address the former above and in the linked spreadsheet, and the latter below. Let me know if there are other concerns.


> The current choice of specification was very intentional, based around
> the limitations and designs of a number of APIs. Quite simply, a
> number of underlying cryptographic libraries do not support what you
> propose - most notably, PKCS#11 - and instead require much more
> specification up front.
> 
> Now we can debate whether we're designing for the past or for the
> future, but I would suspect that if the Web Crypto API requires new
> platform cryptographic APIs to be developed, then the entire value of
> this API is lost (namely, being able to use existing, validated,
> approved-by-various-government APIs and implementations - and to keep
> browsers OUT of the crypto game).
> 
> I'm all for reducing friction, but I want to make sure we recognize
> the limitations in the design space.

Funny enough, what prompted me to get around to writing this up was a question from a developer who's working on implementing this interface on top of NSS :)  

I'd like to make two observations here: First, that the two questions (of how parameters are allocated here vs legacy) are pretty well decoupled.  And second, even if you don't buy the first argument, it doesn't matter, because we're not actually changing what information the API has at any given moment.

On the first point: The points of contact between the WebCrypto API implementation and the underlying crypto implementation are pretty well defined.  In the course of a generateKey / encrypt process (for example), the WebCrypto software has to make two calls to the underlying library, one to generate a key and one to encrypt data.  At both points, it has all the parameters it needs -- it has both the algorithm parameters and the generation/operation parameters, and can use them both to fill in what the library needs. 

For example, suppose you're trying to invoke GCM via PKCS11.  Under this proposal, you would get two separate inputs:
  Algorithm parameters: alg_p = { name: "AES-GCM", tagLength: 128 }
  Operation parameters: op_p = { iv: /* ... */, additionalData: /* ... */ }
You can then fill in the CK_GCM_PARAMS as follows (assuming some JS-to-C conversion layer):
  typedef struct CK_GCM_PARAMS {
    CK_BYTE_PTR pIv;    // op_p.iv
    CK_ULONG ulIvLen;   // op_p.iv.byteLength
    CK_BYTE_PTR pAAD;   // op_p.additionalData
    CK_ULONG ulAADLen;  // op_p.additionalData.byteLength
    CK_ULONG ulTagBits; // alg_p.tagLength
  } CK_GCM_PARAMS;

On the second point: If you don't buy the argument above on the first point, then the current API is broken.  This proposal doesn't change the set of parameters that's presented when a WebCrypto method is invoked, it just changes where the API implementation looks for them.  Namely, for CryptoOperation calls, instead of pulling everything from the algorithm argument, it pulls primarily from the key.algorithm, then adds the operational parameters.  So there's nothing new here.

In case an example helps, one is below.

Hope that this helps clarify,
--Richard


-----BEGIN-example.js-----
//==================================
// OLD: 

// Define parameters
var alg_gen = {
    name: "AES-GCM",
    length: 128
};
var alg_enc = { 
    name: "AES-GCM", 
    tagLength: 128,
    iv: new Uint8Array(12),
    additionalData: new Uint8Array()
};
window.crypto.getRandomValues(alg_enc.iv);

// Make a key and encrypt some data
window.crypto.generateKey(alg_gen, false, ["encrypt"])
    .then(function(k) {
        return window.crypto.encrypt(/* TODO */);
    })
    .then(function(encryptor) {
        /* Encrypt some data */  
    });

//==================================
// NEW: 

// Define parameters
var alg = { name: "AES-GCM", tagLength: 128 };
var gen_p = { length: 128 };
var op_p = {
    iv: new Uint8Array(12),
    additionalData: new Uint8Array()
};
window.crypto.getRandomValues(op_p.iv);

// Make a key and encrypt some data
window.crypto.generateKey(alg, gen_p, false, ["encrypt"])
    .then(function(k) {
        return window.crypto.encrypt(k, op_p);
    })
    .then(function(encryptor) {
        /* Encrypt some data */  
    });
-----END-example.js-----





> 
> 
> 
>> 
>> PROPOSAL
>> ========
>> 
>> At a high level:
>> 1. Split out operation and generation parameteres from algorithm parameters
>> 2. Instead of specifying an algorithm in the CryptoOperation methods, specify key + operation parameters
>> 3. In addition to specifying an algorithm at key generation time, specify algorithm + generation parameters
>> 
>> This resolves the two ambiguities noted above.  At key generation time, the algorithm is copied straight through to the key, while the generation parameters are discarded.  At operation time, the algorithm is drawn from the key, and the operation parameters specified in the method call.
>> 
>> As a bonus, this also adds some clarify the other methods for creating Key objects, deriveKey and importKey.  In those cases, you're not generating the key, so you just have to specify the algorithm.
>> 
>> We might also consider exposing the generation parameters on keys.  That would allow applications to query a Key object for its properties after it's been generated ("How long is this RSA key?").  Applications could keep track of this information on their own, but incorporating it in the Key object would be simple for the API code, and would save apps the bookkeeping.   If we do expose this information, it seems like it should be as a separate property from "algorithm".
> 

Received on Friday, 7 June 2013 20:14:16 UTC