Quelle secmime.c
Sprache: C
/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Stuff specific to S / MIME policy and interoperability .
* Depends on PKCS7 , but there should be no dependency the other way around .
*/
#include "secmime.h"
#include "secoid.h"
#include "pk11func.h"
#include "ciferfam.h" /* for CIPHER_FAMILY symbols */
#include "secasn1.h"
#include "secitem.h"
#include "cert.h"
#include "keyhi.h"
#include "secerr.h"
typedef struct smime_cipher_map_struct {
unsigned long cipher;
SECOidTag algtag;
SECItem *parms;
} smime_cipher_map;
/*
* These are macros because I think some subsequent parameters ,
* like those for RC5 , will want to use them , too , separately .
*/
#define SMIME_DER_INTVAL_16 SEC_ASN1_INTEGER,
0 x01,
0 x10
#define SMIME_DER_INTVAL_40 SEC_ASN1_INTEGER,
0 x01,
0 x28
#define SMIME_DER_INTVAL_64 SEC_ASN1_INTEGER,
0 x01,
0 x40
#define SMIME_DER_INTVAL_128 SEC_ASN1_INTEGER,
0 x02,
0 x00,
0 x80
#ifdef SMIME_DOES_RC5
/* will be needed; quiet unused warning for now */
static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 };
#endif
static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 };
static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 };
static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 };
static SECItem smime_rc2p40 = { siBuffer, smime_int40,
sizeof (smime_int40) };
static SECItem smime_rc2p64 = { siBuffer, smime_int64,
sizeof (smime_int64) };
static SECItem smime_rc2p128 = { siBuffer, smime_int128,
sizeof (smime_int128) };
static smime_cipher_map smime_cipher_maps[] = {
{ SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, &smime_rc2p40 },
{ SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, &smime_rc2p64 },
{ SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, &smime_rc2p128 },
#ifdef SMIME_DOES_RC5
{ SMIME_RC5PAD_64_16_40, SEC_OID_RC5_CBC_PAD, &smime_rc5p40 },
{ SMIME_RC5PAD_64_16_64, SEC_OID_RC5_CBC_PAD, &smime_rc5p64 },
{ SMIME_RC5PAD_64_16_128, SEC_OID_RC5_CBC_PAD, &smime_rc5p128 },
#endif
{ SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL },
{ SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL }
};
/*
* Note , the following value really just needs to be an upper bound
* on the ciphers .
*/
static const int smime_symmetric_count =
sizeof (smime_cipher_maps) /
sizeof (smime_ciphe
r_map);
static unsigned long *smime_prefs, *smime_newprefs;
static int smime_current_pref_index = 0 ;
static PRBool smime_prefs_complete = PR_FALSE;
static PRBool smime_prefs_changed = PR_TRUE;
static unsigned long smime_policy_bits = 0 ;
static int
smime_mapi_by_cipher(unsigned long cipher)
{
int i;
for (i = 0 ; i < smime_symmetric_count; i++) {
if (smime_cipher_maps[i].cipher == cipher)
break ;
}
if (i == smime_symmetric_count)
return -1 ;
return i;
}
/*
* this function locally records the user ' s preference
*/
SECStatus
SECMIME_EnableCipher(long which, int on)
{
unsigned long mask;
if (smime_newprefs == NULL || smime_prefs_complete) {
/*
* This is either the very first time , or we are starting over .
*/
smime_newprefs = (unsigned long *)PORT_ZAlloc(smime_symmetric_count * sizeof (*smime_newprefs));
if (smime_newprefs == NULL)
return SECFailure;
smime_current_pref_index = 0 ;
smime_prefs_complete = PR_FALSE;
}
mask = which & CIPHER_FAMILYID_MASK;
if (mask == CIPHER_FAMILYID_MASK) {
/*
* This call signifies that all preferences have been set .
* Move " newprefs " over , after checking first whether or
* not the new ones are different from the old ones .
*/
if (smime_prefs != NULL) {
if (PORT_Memcmp(smime_prefs, smime_newprefs,
smime_symmetric_count * sizeof (*smime_prefs)) == 0 )
smime_prefs_changed = PR_FALSE;
else
smime_prefs_changed = PR_TRUE;
PORT_Free(smime_prefs);
}
smime_prefs = smime_newprefs;
smime_prefs_complete = PR_TRUE;
return SECSuccess;
}
PORT_Assert(mask == CIPHER_FAMILYID_SMIME);
if (mask != CIPHER_FAMILYID_SMIME) {
/* XXX set an error! */
return SECFailure;
}
if (on) {
PORT_Assert(smime_current_pref_index < smime_symmetric_count);
if (smime_current_pref_index >= smime_symmetric_count) {
/* XXX set an error! */
return SECFailure;
}
smime_newprefs[smime_current_pref_index++] = which;
}
return SECSuccess;
}
/*
* this function locally records the export policy
*/
SECStatus
SECMIME_SetPolicy(long which, int on)
{
unsigned long mask;
PORT_Assert((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME);
if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) {
/* XXX set an error! */
return SECFailure;
}
which &= ~CIPHER_FAMILYID_MASK;
PORT_Assert(which < 32 ); /* bits in the long */
if (which >= 32 ) {
/* XXX set an error! */
return SECFailure;
}
mask = 1 UL << which;
if (on) {
smime_policy_bits |= mask;
} else {
smime_policy_bits &= ~mask;
}
return SECSuccess;
}
/*
* Based on the given algorithm ( including its parameters , in some cases ! )
* and the given key ( may or may not be inspected , depending on the
* algorithm ) , find the appropriate policy algorithm specification
* and return it . If no match can be made , - 1 is returned .
*/
static long
smime_policy_algorithm(SECAlgorithmID *algid, PK11SymKey *key)
{
SECOidTag algtag;
algtag = SECOID_GetAlgorithmTag(algid);
switch (algtag) {
case SEC_OID_RC2_CBC: {
unsigned int keylen_bits;
keylen_bits = PK11_GetKeyStrength(key, algid);
switch (keylen_bits) {
case 40 :
return SMIME_RC2_CBC_40;
case 64 :
return SMIME_RC2_CBC_64;
case 128 :
return SMIME_RC2_CBC_128;
default :
break ;
}
} break ;
case SEC_OID_DES_CBC:
return SMIME_DES_CBC_56;
case SEC_OID_DES_EDE3_CBC:
return SMIME_DES_EDE3_168;
#ifdef SMIME_DOES_RC5
case SEC_OID_RC5_CBC_PAD:
PORT_Assert(0 ); /* XXX need to pull out parameters and match */
break ;
#endif
default :
break ;
}
return -1 ;
}
static PRBool
smime_cipher_allowed(unsigned long which)
{
unsigned long mask;
which &= ~CIPHER_FAMILYID_MASK;
PORT_Assert(which < 32 ); /* bits per long (min) */
if (which >= 32 )
return PR_FALSE;
mask = 1 UL << which;
if ((mask & smime_policy_bits) == 0 )
return PR_FALSE;
return PR_TRUE;
}
PRBool
SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key)
{
long which;
which = smime_policy_algorithm(algid, key);
if (which < 0 )
return PR_FALSE;
return smime_cipher_allowed((unsigned long )which);
}
/*
* Does the current policy allow * any * S / MIME encryption ( or decryption ) ?
*
* This tells whether or not * any * S / MIME encryption can be done ,
* according to policy . Callers may use this to do nicer user interface
* ( say , greying out a checkbox so a user does not even try to encrypt
* a message when they are not allowed to ) or for any reason they want
* to check whether S / MIME encryption ( or decryption , for that matter )
* may be done .
*
* It takes no arguments . The return value is a simple boolean :
* PR_TRUE means encryption ( or decryption ) is * possible *
* ( but may still fail due to other reasons , like because we cannot
* find all the necessary certs , etc . ; PR_TRUE is * not * a guarantee )
* PR_FALSE means encryption ( or decryption ) is not permitted
*
* There are no errors from this routine .
*/
PRBool
SECMIME_EncryptionPossible(void )
{
if (smime_policy_bits != 0 )
return PR_TRUE;
return PR_FALSE;
}
/*
* XXX Would like the " parameters " field to be a SECItem * , but the
* encoder is having trouble with optional pointers to an ANY . Maybe
* once that is fixed , can change this back . . .
*/
typedef struct smime_capability_struct {
unsigned long cipher; /* local; not part of encoding */
SECOidTag capIDTag; /* local; not part of encoding */
SECItem capabilityID;
SECItem parameters;
} smime_capability;
static const SEC_ASN1Template smime_capability_template[] = {
{ SEC_ASN1_SEQUENCE,
0 , NULL, sizeof (smime_capability) },
{ SEC_ASN1_OBJECT_ID,
offsetof(smime_capability, capabilityID) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
offsetof(smime_capability, parameters) },
{ 0 }
};
static const SEC_ASN1Template smime_capabilities_template[] = {
{ SEC_ASN1_SEQUENCE_OF, 0 , smime_capability_template }
};
static void
smime_fill_capability(smime_capability *cap)
{
unsigned long cipher;
SECOidTag algtag;
int i;
algtag = SECOID_FindOIDTag(&(cap->capabilityID));
for (i = 0 ; i < smime_symmetric_count; i++) {
if (smime_cipher_maps[i].algtag != algtag)
continue ;
/*
* XXX If SECITEM_CompareItem allowed NULLs as arguments ( comparing
* 2 NULLs as equal and NULL and non - NULL as not equal ) , we could
* use that here instead of all of the following comparison code .
*/
if (cap->parameters.data != NULL) {
if (smime_cipher_maps[i].parms == NULL)
continue ;
if (cap->parameters.len != smime_cipher_maps[i].parms->len)
continue ;
if (PORT_Memcmp(cap->parameters.data,
smime_cipher_maps[i].parms->data,
cap->parameters.len) == 0 )
break ;
} else if (smime_cipher_maps[i].parms == NULL) {
break ;
}
}
if (i == smime_symmetric_count)
cipher = 0 ;
else
cipher = smime_cipher_maps[i].cipher;
cap->cipher = cipher;
cap->capIDTag = algtag;
}
static long
smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts)
{
PLArenaPool *poolp;
long chosen_cipher;
int *cipher_abilities;
int *cipher_votes;
int strong_mapi;
int rcount, mapi, max;
if (smime_policy_bits == 0 ) {
PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM);
return -1 ;
}
chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */
poolp = PORT_NewArena(1024 ); /* XXX what is right value? */
if (poolp == NULL)
goto done;
cipher_abilities = (int *)PORT_ArenaZAlloc(poolp,
smime_symmetric_count * sizeof (int ));
if (cipher_abilities == NULL)
goto done;
cipher_votes = (int *)PORT_ArenaZAlloc(poolp,
smime_symmetric_count * sizeof (int ));
if (cipher_votes == NULL)
goto done;
/*
* XXX Should have a # define somewhere which specifies default
* strong cipher . ( Or better , a way to configure . )
*/
/* Make triple-DES the strong cipher. */
strong_mapi = smime_mapi_by_cipher(SMIME_DES_EDE3_168);
PORT_Assert(strong_mapi >= 0 );
for (rcount = 0 ; rcerts[rcount] != NULL; rcount++) {
SECItem *profile;
smime_capability **caps;
int capi, pref;
SECStatus dstat;
pref = smime_symmetric_count;
profile = CERT_FindSMimeProfile(rcerts[rcount]);
if (profile != NULL && profile->data != NULL && profile->len > 0 ) {
caps = NULL;
dstat = SEC_QuickDERDecodeItem(poolp, &caps,
smime_capabilities_template,
profile);
if (dstat == SECSuccess && caps != NULL) {
for (capi = 0 ; caps[capi] != NULL; capi++) {
smime_fill_capability(caps[capi]);
mapi = smime_mapi_by_cipher(caps[capi]->cipher);
if (mapi >= 0 ) {
cipher_abilities[mapi]++;
cipher_votes[mapi] += pref;
--pref;
}
}
}
} else {
SECKEYPublicKey *key;
unsigned int pklen_bits;
/*
* XXX This is probably only good for RSA keys . What I would
* really like is a function to just say ; Is the public key in
* this cert an export - length key ? Then I would not have to
* know things like the value 512 , or the kind of key , or what
* a subjectPublicKeyInfo is , etc .
*/
key = CERT_ExtractPublicKey(rcerts[rcount]);
if (key != NULL) {
pklen_bits = SECKEY_PublicKeyStrength(key) * 8 ;
SECKEY_DestroyPublicKey(key);
if (pklen_bits > 512 ) {
cipher_abilities[strong_mapi]++;
cipher_votes[strong_mapi] += pref;
}
}
}
if (profile != NULL)
SECITEM_FreeItem(profile, PR_TRUE);
}
max = 0 ;
for (mapi = 0 ; mapi < smime_symmetric_count; mapi++) {
if (cipher_abilities[mapi] != rcount)
continue ;
if (!smime_cipher_allowed(smime_cipher_maps[mapi].cipher))
continue ;
if (cipher_votes[mapi] > max) {
chosen_cipher = smime_cipher_maps[mapi].cipher;
max = cipher_votes[mapi];
} /* XXX else if a tie, let scert break it? */
}
done:
if (poolp != NULL)
PORT_FreeArena(poolp, PR_FALSE);
return chosen_cipher;
}
/*
* XXX This is a hack for now to satisfy our current interface .
* Eventually , with more parameters needing to be specified , just
* looking up the keysize is not going to be sufficient .
*/
static int
smime_keysize_by_cipher(unsigned long which)
{
int keysize;
switch (which) {
case SMIME_RC2_CBC_40:
keysize = 40 ;
break ;
case SMIME_RC2_CBC_64:
keysize = 64 ;
break ;
case SMIME_RC2_CBC_128:
keysize = 128 ;
break ;
#ifdef SMIME_DOES_RC5
case SMIME_RC5PAD_64_16_40:
case SMIME_RC5PAD_64_16_64:
case SMIME_RC5PAD_64_16_128:
/* XXX See comment above; keysize is not enough... */
PORT_Assert(0 );
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
keysize = -1 ;
break ;
#endif
case SMIME_DES_CBC_56:
case SMIME_DES_EDE3_168:
/*
* These are special ; since the key size is fixed , we actually
* want to * avoid * specifying a key size .
*/
keysize = 0 ;
break ;
default :
keysize = -1 ;
break ;
}
return keysize;
}
/*
* Start an S / MIME encrypting context .
*
* " scert " is the cert for the sender . It will be checked for validity .
* " rcerts " are the certs for the recipients . They will also be checked .
*
* " certdb " is the cert database to use for verifying the certs .
* It can be NULL if a default database is available ( like in the client ) .
*
* This function already does all of the stuff specific to S / MIME protocol
* and local policy ; the return value just needs to be passed to
* SEC_PKCS7Encode ( ) or to SEC_PKCS7EncoderStart ( ) to create the encoded data ,
* and finally to SEC_PKCS7DestroyContentInfo ( ) .
*
* An error results in a return value of NULL and an error set .
* ( Retrieve specific errors via PORT_GetError ( ) / XP_GetError ( ) . )
*/
SEC_PKCS7ContentInfo *
SECMIME_CreateEncrypted(CERTCertificate *scert,
CERTCertificate **rcerts,
CERTCertDBHandle *certdb,
SECKEYGetPasswordKey pwfn,
void *pwfn_arg)
{
SEC_PKCS7ContentInfo *cinfo;
long cipher;
SECOidTag encalg;
int keysize;
int mapi, rci;
cipher = smime_choose_cipher(scert, rcerts);
if (cipher < 0 )
return NULL;
mapi = smime_mapi_by_cipher(cipher);
if (mapi < 0 )
return NULL;
/*
* XXX This is stretching it - - CreateEnvelopedData should probably
* take a cipher itself of some sort , because we cannot know what the
* future will bring in terms of parameters for each type of algorithm .
* For example , just an algorithm and keysize is * not * sufficient to
* fully specify the usage of RC5 ( which also needs to know rounds and
* block size ) . Work this out into a better API !
*/
encalg = smime_cipher_maps[mapi].algtag;
keysize = smime_keysize_by_cipher(cipher);
if (keysize < 0 )
return NULL;
cinfo = SEC_PKCS7CreateEnvelopedData(scert, certUsageEmailRecipient,
certdb, encalg, keysize,
pwfn, pwfn_arg);
if (cinfo == NULL)
return NULL;
for (rci = 0 ; rcerts[rci] != NULL; rci++) {
if (rcerts[rci] == scert)
continue ;
if (SEC_PKCS7AddRecipient(cinfo, rcerts[rci], certUsageEmailRecipient,
NULL) != SECSuccess) {
SEC_PKCS7DestroyContentInfo(cinfo);
return NULL;
}
}
return cinfo;
}
static smime_capability **smime_capabilities;
static SECItem *smime_encoded_caps;
static SECStatus
smime_init_caps(void )
{
smime_capability *cap;
smime_cipher_map *map;
SECOidData *oiddata;
SECStatus rv;
int i;
if (smime_encoded_caps != NULL && (!smime_prefs_changed))
return SECSuccess;
if (smime_encoded_caps != NULL) {
SECITEM_FreeItem(smime_encoded_caps, PR_TRUE);
smime_encoded_caps = NULL;
}
if (smime_capabilities == NULL) {
smime_capabilities = (smime_capability **)PORT_ZAlloc(
(smime_symmetric_count + 1 ) * sizeof (smime_capability *));
if (smime_capabilities == NULL)
return SECFailure;
}
rv = SECFailure;
/*
The process of creating the encoded PKCS7 cipher capability list
involves two basic steps :
( a ) Convert our internal representation of cipher preferences
( smime_prefs ) into an array containing cipher OIDs and
parameter data ( smime_capabilities ) . This step is
performed here .
( b ) Encode , using ASN . 1 , the cipher information in
smime_capabilities , leaving the encoded result in
smime_encoded_caps .
( In the process of performing ( a ) , Lisa put in some optimizations
which allow us to avoid needlessly re - populating elements in
smime_capabilities as we walk through smime_prefs . )
*/
for (i = 0 ; i < smime_current_pref_index; i++) {
int mapi;
/* Get the next cipher preference in smime_prefs. */
mapi = smime_mapi_by_cipher(smime_prefs[i]);
if (mapi < 0 )
break ;
/* Find the corresponding entry in the cipher map. */
PORT_Assert(mapi < smime_symmetric_count);
map = &(smime_cipher_maps[mapi]);
/*
* Convert the next preference found in smime_prefs into an
* smime_capability .
*/
cap = smime_capabilities[i];
if (cap == NULL) {
cap = (smime_capability *)PORT_ZAlloc(sizeof (smime_capability));
if (cap == NULL)
break ;
smime_capabilities[i] = cap;
} else if (cap->cipher == smime_prefs[i]) {
continue ; /* no change to this one */
}
cap->capIDTag = map->algtag;
oiddata = SECOID_FindOIDByTag(map->algtag);
if (oiddata == NULL)
break ;
if (cap->capabilityID.data != NULL) {
SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE);
cap->capabilityID.data = NULL;
cap->capabilityID.len = 0 ;
}
rv = SECITEM_CopyItem(NULL, &(cap->capabilityID), &(oiddata->oid));
if (rv != SECSuccess)
break ;
if (map->parms == NULL) {
cap->parameters.data = NULL;
cap->parameters.len = 0 ;
} else {
cap->parameters.data = map->parms->data;
cap->parameters.len = map->parms->len;
}
cap->cipher = smime_prefs[i];
}
if (i != smime_current_pref_index)
return rv;
while (i < smime_symmetric_count) {
cap = smime_capabilities[i];
if (cap != NULL) {
SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE);
PORT_Free(cap);
}
smime_capabilities[i] = NULL;
i++;
}
smime_capabilities[i] = NULL;
smime_encoded_caps = SEC_ASN1EncodeItem(NULL, NULL, &smime_capabilities,
smime_capabilities_template);
if (smime_encoded_caps == NULL)
return SECFailure;
return SECSuccess;
}
static SECStatus
smime_add_profile(CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo)
{
PORT_Assert(smime_prefs_complete);
if (!smime_prefs_complete)
return SECFailure;
/* For that matter, if capabilities haven't been initialized yet,
do so now. */
if (smime_encoded_caps == NULL || smime_prefs_changed) {
SECStatus rv;
rv = smime_init_caps();
if (rv != SECSuccess)
return rv;
PORT_Assert(smime_encoded_caps != NULL);
}
return SEC_PKCS7AddSignedAttribute(cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES,
smime_encoded_caps);
}
/*
* Start an S / MIME signing context .
*
* " scert " is the cert that will be used to sign the data . It will be
* checked for validity .
*
* " ecert " is the signer ' s encryption cert . If it is different from
* scert , then it will be included in the signed message so that the
* recipient can save it for future encryptions .
*
* " certdb " is the cert database to use for verifying the cert .
* It can be NULL if a default database is available ( like in the client ) .
*
* " digestalg " names the digest algorithm ( e . g . SEC_OID_SHA1 ) .
* XXX There should be SECMIME functions for hashing , or the hashing should
* be built into this interface , which we would like because we would
* support more smartcards that way , and then this argument should go away . )
*
* " digest " is the actual digest of the data . It must be provided in
* the case of detached data or NULL if the content will be included .
*
* This function already does all of the stuff specific to S / MIME protocol
* and local policy ; the return value just needs to be passed to
* SEC_PKCS7Encode ( ) or to SEC_PKCS7EncoderStart ( ) to create the encoded data ,
* and finally to SEC_PKCS7DestroyContentInfo ( ) .
*
* An error results in a return value of NULL and an error set .
* ( Retrieve specific errors via PORT_GetError ( ) / XP_GetError ( ) . )
*/
SEC_PKCS7ContentInfo *
SECMIME_CreateSigned(CERTCertificate *scert,
CERTCertificate *ecert,
CERTCertDBHandle *certdb,
SECOidTag digestalg,
SECItem *digest,
SECKEYGetPasswordKey pwfn,
void *pwfn_arg)
{
SEC_PKCS7ContentInfo *cinfo;
SECStatus rv;
/* See note in header comment above about digestalg. */
/* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */
cinfo = SEC_PKCS7CreateSignedData(scert, certUsageEmailSigner,
certdb, digestalg, digest,
pwfn, pwfn_arg);
if (cinfo == NULL)
return NULL;
if (SEC_PKCS7IncludeCertChain(cinfo, NULL) != SECSuccess) {
SEC_PKCS7DestroyContentInfo(cinfo);
return NULL;
}
/* if the encryption cert and the signing cert differ, then include
* the encryption cert too .
*/
/* it is ok to compare the pointers since we ref count, and the same
* cert will always have the same pointer
*/
if ((ecert != NULL) && (ecert != scert)) {
rv = SEC_PKCS7AddCertificate(cinfo, ecert);
if (rv != SECSuccess) {
SEC_PKCS7DestroyContentInfo(cinfo);
return NULL;
}
}
/*
* Add the signing time . But if it fails for some reason ,
* may as well not give up altogether - - just assert .
*/
rv = SEC_PKCS7AddSigningTime(cinfo);
PORT_Assert(rv == SECSuccess);
/*
* Add the email profile . Again , if it fails for some reason ,
* may as well not give up altogether - - just assert .
*/
rv = smime_add_profile(ecert, cinfo);
PORT_Assert(rv == SECSuccess);
return cinfo;
}
Messung V0.5 in Prozent C=95 H=93 G=93
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=2039723